From ec47f16cc457539e961b2a327fcf12b4ff2284e4 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 02:18:37 -0800 Subject: [PATCH 1/9] Refactoring d2ds1 * Adding setters/getters so that state management can be maintained internally when the ds1 struct is altered * Adding unit tests for DS1 --- d2common/d2fileformats/d2ds1/ds1.go | 573 ++++++++++++++---- d2common/d2fileformats/d2ds1/ds1_test.go | 289 +++++++++ .../d2ds1/floor_shadow_record.go | 16 +- d2common/d2fileformats/d2ds1/object.go | 10 + .../d2ds1/substitution_record.go | 4 +- d2common/d2fileformats/d2ds1/tile_record.go | 21 +- d2common/d2fileformats/d2ds1/wall_record.go | 10 +- d2core/d2config/defaults.go | 6 +- d2core/d2map/d2mapengine/engine.go | 4 +- d2core/d2records/level_presets_loader.go | 2 +- utils/extract-mpq/doc.go | 2 +- 11 files changed, 782 insertions(+), 155 deletions(-) create mode 100644 d2common/d2fileformats/d2ds1/ds1_test.go diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 1e54b329..ddcadec3 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -39,34 +39,347 @@ const ( // DS1 represents the "stamp" data that is used to build up maps. type DS1 struct { - Files []string // FilePtr table of file string pointers - Objects []Object // Objects - Tiles [][]TileRecord // The tile data for the DS1 - SubstitutionGroups []SubstitutionGroup // Substitution groups for the DS1 - 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 - 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 - unknown1 []byte - LayerStreamTypes []d2enum.LayerStreamType - unknown2 uint32 - NpcIndexes []int + files []string // FilePtr table of file string pointers + objects []Object // objects + tiles [][]Tile // The tile data for the DS1 + substitutionGroups []SubstitutionGroup // Substitution groups for the DS1 + 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 + numberOfWallLayers int32 // WallNum number of wall & orientation layers used + numberOfFloorLayers 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 + + dirty bool // when modifying tiles, need to perform upkeep on ds1 state + unknown1 []byte + layerStreamTypes []d2enum.LayerStreamType + unknown2 uint32 + npcIndexes []int +} + +// Files returns a list of file path strings. These correspond to DT1 paths that this DS1 will use. +func (ds1 *DS1) Files() []string { + return ds1.files +} + +// AddFile adds a file path to the list of file paths +func (ds1 *DS1) AddFile(file string) { + if ds1.files == nil { + ds1.files = make([]string, 0) + } + + ds1.files = append(ds1.files, file) +} + +// RemoveFile removes a file from the files slice +func (ds1 *DS1) RemoveFile(file string) { + for idx := range ds1.files { + if ds1.files[idx] == file { + ds1.files = append(ds1.files[:idx], ds1.files[idx+1:]...) + break + } + } +} + +// Objects returns the slice of objects found in this ds1 +func (ds1 *DS1) Objects() []Object { + return ds1.objects +} + +// AddObject adds an object to this ds1 +func (ds1 *DS1) AddObject(obj Object) { + if ds1.objects == nil { + ds1.objects = make([]Object, 0) + } + + ds1.objects = append(ds1.objects, obj) +} + +// RemoveObject removes the first equivalent object found in this ds1's object list +func (ds1 *DS1) RemoveObject(obj Object) { + for idx := range ds1.objects { + if ds1.objects[idx].Equals(&obj) { + ds1.objects = append(ds1.objects[:idx], ds1.objects[idx+1:]...) + break + } + } +} + +func defaultTiles() [][]Tile { + return [][]Tile{{makeDefaultTile()}} +} + +// Tiles returns the 2-dimensional (y,x) slice of tiles +func (ds1 *DS1) Tiles() [][]Tile { + if ds1.tiles == nil { + ds1.SetTiles(defaultTiles()) + } + + return ds1.tiles +} + +// SetTiles sets the 2-dimensional (y,x) slice of tiles for this ds1 +func (ds1 *DS1) SetTiles(tiles [][]Tile) { + if len(tiles) == 0 { + tiles = defaultTiles() + } + + ds1.tiles = tiles + ds1.dirty = true +} + +// Tile returns the tile at the given x,y tile coordinate (nil if x,y is out of bounds) +func (ds1 *DS1) Tile(x, y int) *Tile { + if y >= len(ds1.tiles) { + return nil + } + + if x >= len(ds1.tiles[y]) { + return nil + } + + return &ds1.tiles[y][x] +} + +// SetTile sets the tile at the given tile x,y coordinates +func (ds1 *DS1) SetTile(x, y int, t *Tile) { + if ds1.Tile(x, y) == nil { + return + } + + ds1.tiles[y][x] = *t + ds1.dirty = true +} + +// Version returns the ds1's version +func (ds1 *DS1) Version() int { + return int(ds1.version) +} + +// SetVersion sets the ds1's version +func (ds1 *DS1) SetVersion(v int) { + ds1.version = int32(v) +} + +// Width returns te ds1's width +func (ds1 *DS1) Width() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.width) +} + +// SetWidth sets the ds1's width +func (ds1 *DS1) SetWidth(w int) { + if w <= 1 { + w = 1 + } + + if int(ds1.width) == w { + return + } + + ds1.dirty = true // we know we're about to edit this ds1 + defer ds1.update() + + for rowIdx := range ds1.tiles { + // if the row has too many tiles + if len(ds1.tiles[rowIdx]) > w { + // remove the extras + ds1.tiles[rowIdx] = ds1.tiles[rowIdx][:w] + } + + // if the row doesn't have enough tiles + if len(ds1.tiles[rowIdx]) < w { + // figure out how many more we need + numNeeded := w - len(ds1.tiles[rowIdx]) + newTiles := make([]Tile, numNeeded) + + // make new default tiles + for idx := range newTiles { + newTiles[idx] = makeDefaultTile() + } + + // add them to this ds1 + ds1.tiles[rowIdx] = append(ds1.tiles[rowIdx], newTiles...) + } + } +} + +// Height returns te ds1's height +func (ds1 *DS1) Height() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.height) +} + +// SetHeight sets the ds1's height +func (ds1 *DS1) SetHeight(h int) { + if h <= 1 { + h = 1 + } + + if int(ds1.height) == h { + return + } + + if len(ds1.tiles) < h { + ds1.dirty = true // we know we're about to edit this ds1 + defer ds1.update() + + // figure out how many more rows we need + numRowsNeeded := h - len(ds1.tiles) + newRows := make([][]Tile, numRowsNeeded) + + // populate the new rows with tiles + for rowIdx := range newRows { + newRows[rowIdx] = make([]Tile, ds1.width) + + for colIdx := range newRows[rowIdx] { + newRows[rowIdx][colIdx] = makeDefaultTile() + } + } + } + + // if the ds1 has too many rows + if len(ds1.tiles) > h { + ds1.dirty = true // we know we're about to edit this ds1 + defer ds1.update() + + // remove the extras + ds1.tiles = ds1.tiles[:h] + } +} + +// Size returns te ds1's size (width, height) +func (ds1 *DS1) Size() (w, h int) { + if ds1.dirty { + ds1.update() + } + + return int(ds1.width), int(ds1.height) +} + +// SetSize sets the ds1's size (width,height) +func (ds1 *DS1) SetSize(w, h int) { + ds1.SetWidth(w) + ds1.SetHeight(h) +} + +// Act returns the ds1's act +func (ds1 *DS1) Act() int { + return int(ds1.act) +} + +// SetAct sets the ds1's act +func (ds1 *DS1) SetAct(act int) { + if act < 0 { + act = 0 + } + + ds1.act = int32(act) +} + +// SubstitutionType returns the ds1's subtitution type +func (ds1 *DS1) SubstitutionType() int { + return int(ds1.substitutionType) +} + +// SetSubstitutionType sets the ds1's subtitution type +func (ds1 *DS1) SetSubstitutionType(t int) { + ds1.substitutionType = int32(t) +} + +// NumberOfWallLayers returns the number of wall layers per tile +func (ds1 *DS1) NumberOfWallLayers() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.numberOfWallLayers) +} + +// NumberOfFloorLayers returns the number of floor layers per tile +func (ds1 *DS1) NumberOfFloorLayers() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.numberOfFloorLayers) +} + +// NumberOfShadowLayers returns the number of shadow layers per tile +func (ds1 *DS1) NumberOfShadowLayers() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.numberOfShadowLayers) +} + +// NumberOfSubstitutionLayers returns the number of substitution layers per tile +func (ds1 *DS1) NumberOfSubstitutionLayers() int { + if ds1.dirty { + ds1.update() + } + + return int(ds1.numberOfSubstitutionLayers) +} + +// SubstitutionGroups returns the number of wall layers per tile +func (ds1 *DS1) SubstitutionGroups() []SubstitutionGroup { + return ds1.substitutionGroups +} + +// SetSubstitutionGroups sets the substitution groups for the ds1 +func (ds1 *DS1) SetSubstitutionGroups(groups []SubstitutionGroup) { + ds1.substitutionGroups = groups +} + +func (ds1 *DS1) update() { + ds1.ensureAtLeastOneTile() + ds1.enforceAllTileLayersMatch() + ds1.updateLayerCounts() + + ds1.SetSize(len(ds1.tiles[0]), len(ds1.tiles)) + + ds1.dirty = false +} + +func (ds1 *DS1) ensureAtLeastOneTile() { + // guarantee at least one tile exists + if len(ds1.tiles) == 0 { + ds1.tiles = [][]Tile{{makeDefaultTile()}} + } +} + +func (ds1 *DS1) enforceAllTileLayersMatch() { + +} + +func (ds1 *DS1) updateLayerCounts() { + t := ds1.tiles[0][0] // the one tile that is guaranteed to exist + ds1.numberOfFloorLayers = int32(len(t.Floors)) + ds1.numberOfShadowLayers = int32(len(t.Shadows)) + ds1.numberOfWallLayers = int32(len(t.Walls)) + ds1.numberOfSubstitutionLayers = int32(len(t.Substitutions)) } // LoadDS1 loads the specified DS1 file func LoadDS1(fileData []byte) (*DS1, error) { ds1 := &DS1{ - Act: 1, - NumberOfFloors: 0, - NumberOfWalls: 0, - NumberOfShadowLayers: 1, - NumberOfSubstitutionLayers: 0, + act: 1, + numberOfFloorLayers: 0, + numberOfWallLayers: 0, + numberOfShadowLayers: 1, + numberOfSubstitutionLayers: 0, } br := d2datautils.CreateStreamReader(fileData) @@ -78,7 +391,7 @@ func LoadDS1(fileData []byte) (*DS1, error) { return nil, err } - if ds1.Version >= v9 && ds1.Version <= v13 { + if ds1.version >= v9 && ds1.version <= v13 { // Skipping two dwords because they are "meaningless"? ds1.unknown1, err = br.ReadBytes(unknown1BytesCount) if err != nil { @@ -86,33 +399,33 @@ func LoadDS1(fileData []byte) (*DS1, error) { } } - if ds1.Version >= v4 { - ds1.NumberOfWalls, err = br.ReadInt32() + if ds1.version >= v4 { + ds1.numberOfWallLayers, err = br.ReadInt32() if err != nil { return nil, err } - if ds1.Version >= v16 { - ds1.NumberOfFloors, err = br.ReadInt32() + if ds1.version >= v16 { + ds1.numberOfFloorLayers, err = br.ReadInt32() if err != nil { return nil, err } } else { - ds1.NumberOfFloors = 1 + ds1.numberOfFloorLayers = 1 } } - ds1.LayerStreamTypes = ds1.setupStreamLayerTypes() + ds1.layerStreamTypes = ds1.setupStreamLayerTypes() - ds1.Tiles = make([][]TileRecord, ds1.Height) + ds1.tiles = make([][]Tile, 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 y := range ds1.tiles { + ds1.tiles[y] = make([]Tile, ds1.width) + for x := 0; x < int(ds1.width); x++ { + ds1.tiles[y][x].Walls = make([]Wall, ds1.numberOfWallLayers) + ds1.tiles[y][x].Floors = make([]Floor, ds1.numberOfFloorLayers) + ds1.tiles[y][x].Shadows = make([]Shadow, ds1.numberOfShadowLayers) + ds1.tiles[y][x].Substitutions = make([]Substitution, ds1.numberOfSubstitutionLayers) } } @@ -121,7 +434,7 @@ func LoadDS1(fileData []byte) (*DS1, error) { return nil, err } - err = ds1.loadObjects(br) + err = ds1.loadobjects(br) if err != nil { return nil, err } @@ -142,41 +455,41 @@ func LoadDS1(fileData []byte) (*DS1, error) { func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { var err error - ds1.Version, err = br.ReadInt32() + ds1.version, err = br.ReadInt32() if err != nil { return err } - ds1.Width, err = br.ReadInt32() + ds1.width, err = br.ReadInt32() if err != nil { return err } - ds1.Height, err = br.ReadInt32() + ds1.height, err = br.ReadInt32() if err != nil { return err } - ds1.Width++ - ds1.Height++ + ds1.width++ + ds1.height++ - if ds1.Version >= v8 { - ds1.Act, err = br.ReadInt32() + if ds1.version >= v8 { + ds1.act, err = br.ReadInt32() if err != nil { return err } - ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1) + ds1.act = d2math.MinInt32(d2enum.ActsNumber, ds1.act+1) } - if ds1.Version >= v10 { - ds1.SubstitutionType, err = br.ReadInt32() + if ds1.version >= v10 { + ds1.substitutionType, err = br.ReadInt32() if err != nil { return err } - if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 { - ds1.NumberOfSubstitutionLayers = 1 + if ds1.substitutionType == 1 || ds1.substitutionType == 2 { + ds1.numberOfSubstitutionLayers = 1 } } @@ -189,17 +502,17 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { } func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { - if ds1.Version >= v3 { + 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) + ds1.files = make([]string, numberOfFiles) for i := 0; i < int(numberOfFiles); i++ { - ds1.Files[i] = "" + ds1.files[i] = "" for { ch, err := br.ReadByte() @@ -211,7 +524,7 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { break } - ds1.Files[i] += string(ch) + ds1.files[i] += string(ch) } } } @@ -219,18 +532,18 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { return nil } -func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { - if ds1.Version < v2 { - ds1.Objects = make([]Object, 0) +func (ds1 *DS1) loadobjects(br *d2datautils.StreamReader) error { + if ds1.version < v2 { + ds1.objects = make([]Object, 0) } else { - numberOfObjects, err := br.ReadInt32() + numberOfobjects, err := br.ReadInt32() if err != nil { return err } - ds1.Objects = make([]Object, numberOfObjects) + ds1.objects = make([]Object, numberOfobjects) - for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ { + for objIdx := 0; objIdx < int(numberOfobjects); objIdx++ { obj := Object{} objType, err := br.ReadInt32() if err != nil { @@ -263,7 +576,7 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { obj.Y = int(objY) obj.Flags = int(objFlags) - ds1.Objects[objIdx] = obj + ds1.objects[objIdx] = obj } } @@ -273,14 +586,14 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { var err error - hasSubstitutions := ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) + hasSubstitutions := ds1.version >= v12 && (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) if !hasSubstitutions { - ds1.SubstitutionGroups = make([]SubstitutionGroup, 0) + ds1.substitutionGroups = make([]SubstitutionGroup, 0) return nil } - if ds1.Version >= v18 { + if ds1.version >= v18 { ds1.unknown2, err = br.ReadUInt32() if err != nil { return err @@ -292,7 +605,7 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { return err } - ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups) + ds1.substitutionGroups = make([]SubstitutionGroup, numberOfSubGroups) for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ { newSub := SubstitutionGroup{} @@ -322,7 +635,7 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { return err } - ds1.SubstitutionGroups[subIdx] = newSub + ds1.substitutionGroups[subIdx] = newSub } return err @@ -331,7 +644,7 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { var layerStream []d2enum.LayerStreamType - if ds1.Version < v4 { + if ds1.version < v4 { layerStream = []d2enum.LayerStreamType{ d2enum.LayerStreamWall1, d2enum.LayerStreamFloor1, @@ -342,23 +655,23 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { } else { // nolint:gomnd // constant layerStream = make([]d2enum.LayerStreamType, - (ds1.NumberOfWalls*2)+ds1.NumberOfFloors+ds1.NumberOfShadowLayers+ds1.NumberOfSubstitutionLayers) + (ds1.numberOfWallLayers*2)+ds1.numberOfFloorLayers+ds1.numberOfShadowLayers+ds1.numberOfSubstitutionLayers) layerIdx := 0 - for i := 0; i < int(ds1.NumberOfWalls); i++ { + for i := 0; i < int(ds1.numberOfWallLayers); 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++ { + for i := 0; i < int(ds1.numberOfFloorLayers); i++ { layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamFloor1) + i) layerIdx++ } - if ds1.NumberOfShadowLayers > 0 { + if ds1.numberOfShadowLayers > 0 { layerStream[layerIdx] = d2enum.LayerStreamShadow layerIdx++ } - if ds1.NumberOfSubstitutionLayers > 0 { + if ds1.numberOfSubstitutionLayers > 0 { layerStream[layerIdx] = d2enum.LayerStreamSubstitute } } @@ -369,7 +682,7 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { var err error - if ds1.Version < v14 { + if ds1.version < v14 { return err } @@ -396,10 +709,10 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { objIdx := -1 - for idx, ds1Obj := range ds1.Objects { + for idx, ds1Obj := range ds1.objects { if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) { objIdx = idx - ds1.NpcIndexes = append(ds1.NpcIndexes, idx) + ds1.npcIndexes = append(ds1.npcIndexes, idx) break } @@ -411,7 +724,7 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { return err } } else { - if ds1.Version >= v15 { + if ds1.version >= v15 { br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data } else { br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data @@ -425,8 +738,8 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) error { var err error - if ds1.Objects[objIdx].Paths == nil { - ds1.Objects[objIdx].Paths = make([]d2path.Path, numPaths) + if ds1.objects[objIdx].Paths == nil { + ds1.objects[objIdx].Paths = make([]d2path.Path, numPaths) } for pathIdx := 0; pathIdx < numPaths; pathIdx++ { @@ -444,7 +757,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) newPath.Position = d2vector.NewPosition(float64(px), float64(py)) - if ds1.Version >= v15 { + if ds1.version >= v15 { action, err := br.ReadInt32() if err != nil { return err @@ -453,7 +766,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) newPath.Action = int(action) } - ds1.Objects[objIdx].Paths[pathIdx] = newPath + ds1.objects[objIdx].Paths[pathIdx] = newPath } return err @@ -468,11 +781,11 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { 0x0F, 0x10, 0x11, 0x12, 0x14, } - for lIdx := range ds1.LayerStreamTypes { - layerStreamType := ds1.LayerStreamTypes[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++ { + for y := 0; y < int(ds1.height); y++ { + for x := 0; x < int(ds1.width); x++ { dw, err := br.ReadUInt32() //nolint:govet // i want to re-use the err variable... if err != nil { return err @@ -481,27 +794,27 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { switch layerStreamType { case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - ds1.Tiles[y][x].Walls[wallIndex].Decode(dw) + 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 & wallTypeBitmask) - if ds1.Version < v7 { + if ds1.version < v7 { if c < int32(len(dirLookup)) { c = dirLookup[c] } } - ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c) - ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset) + ds1.tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c) + 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].Decode(dw) + ds1.tiles[y][x].Floors[floorIndex].Decode(dw) case d2enum.LayerStreamShadow: - ds1.Tiles[y][x].Shadows[0].Decode(dw) + ds1.tiles[y][x].Shadows[0].Decode(dw) case d2enum.LayerStreamSubstitute: - ds1.Tiles[y][x].Substitutions[0].Unknown = dw + ds1.tiles[y][x].Substitutions[0].Unknown = dw } } } @@ -516,22 +829,22 @@ func (ds1 *DS1) Marshal() []byte { sw := d2datautils.CreateStreamWriter() // Step 1 - encode header - sw.PushInt32(ds1.Version) - sw.PushInt32(ds1.Width - 1) - sw.PushInt32(ds1.Height - 1) + 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 >= v8 { + sw.PushInt32(ds1.act - 1) } - if ds1.Version >= v10 { - sw.PushInt32(ds1.SubstitutionType) + if ds1.version >= v10 { + sw.PushInt32(ds1.substitutionType) } - if ds1.Version >= v3 { - sw.PushInt32(int32(len(ds1.Files))) + if ds1.version >= v3 { + sw.PushInt32(int32(len(ds1.files))) - for _, i := range ds1.Files { + for _, i := range ds1.files { sw.PushBytes([]byte(i)...) // separator @@ -539,15 +852,15 @@ func (ds1 *DS1) Marshal() []byte { } } - if ds1.Version >= v9 && ds1.Version <= v13 { + if ds1.version >= v9 && ds1.version <= v13 { sw.PushBytes(ds1.unknown1...) } - if ds1.Version >= v4 { - sw.PushInt32(ds1.NumberOfWalls) + if ds1.version >= v4 { + sw.PushInt32(ds1.numberOfWallLayers) - if ds1.Version >= v16 { - sw.PushInt32(ds1.NumberOfFloors) + if ds1.version >= v16 { + sw.PushInt32(ds1.numberOfFloorLayers) } } @@ -555,10 +868,10 @@ func (ds1 *DS1) Marshal() []byte { ds1.encodeLayers(sw) // Step 3 - encode objects - if !(ds1.Version < v2) { - sw.PushInt32(int32(len(ds1.Objects))) + if !(ds1.version < v2) { + sw.PushInt32(int32(len(ds1.objects))) - for _, i := range ds1.Objects { + for _, i := range ds1.objects { sw.PushUint32(uint32(i.Type)) sw.PushUint32(uint32(i.ID)) sw.PushUint32(uint32(i.X)) @@ -568,12 +881,12 @@ func (ds1 *DS1) Marshal() []byte { } // Step 4 - encode substitutions - if ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) { + if ds1.version >= v12 && (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) { sw.PushUint32(ds1.unknown2) - sw.PushUint32(uint32(len(ds1.SubstitutionGroups))) + sw.PushUint32(uint32(len(ds1.substitutionGroups))) - for _, i := range ds1.SubstitutionGroups { + for _, i := range ds1.substitutionGroups { sw.PushInt32(i.TileX) sw.PushInt32(i.TileY) sw.PushInt32(i.WidthInTiles) @@ -589,31 +902,31 @@ func (ds1 *DS1) Marshal() []byte { } func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { - for lIdx := range ds1.LayerStreamTypes { - layerStreamType := ds1.LayerStreamTypes[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++ { + 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) + 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 + 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) + ds1.tiles[y][x].Floors[floorIndex].Encode(sw) case d2enum.LayerStreamShadow: - ds1.Tiles[y][x].Shadows[0].Encode(sw) + ds1.tiles[y][x].Shadows[0].Encode(sw) case d2enum.LayerStreamSubstitute: - sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown) + sw.PushUint32(ds1.tiles[y][x].Substitutions[0].Unknown) } } } @@ -622,19 +935,19 @@ func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { // Step 5.1 - encode npc's - sw.PushUint32(uint32(len(ds1.NpcIndexes))) + 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 _, 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 { + for _, j := range ds1.objects[i].Paths { sw.PushUint32(uint32(j.Position.X())) sw.PushUint32(uint32(j.Position.Y())) - if ds1.Version >= v15 { + if ds1.version >= v15 { sw.PushUint32(uint32(j.Action)) } } diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go new file mode 100644 index 00000000..9152e70a --- /dev/null +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -0,0 +1,289 @@ +package d2ds1 + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "testing" +) + +func exampleDS1() *DS1 { + return &DS1{ + files: []string{"a.dt1", "b.dt1"}, + objects: []Object{ + {0, 0, 0, 0, 0, nil}, + {0, 1, 0, 0, 0, nil}, + {0, 2, 0, 0, 0, nil}, + {0, 3, 0, 0, 0, nil}, + }, + tiles: [][]Tile{ // 2x2 + { + Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + }, + { + Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + }, + }, + substitutionGroups: nil, + version: 17, + width: 2, + height: 2, + act: 1, + substitutionType: 0, + numberOfWallLayers: 1, + numberOfFloorLayers: 1, + numberOfShadowLayers: 1, + numberOfSubstitutionLayers: 1, + layerStreamTypes: []d2enum.LayerStreamType{ + d2enum.LayerStreamWall1, + d2enum.LayerStreamOrientation1, + d2enum.LayerStreamFloor1, + d2enum.LayerStreamShadow, + }, + npcIndexes: []int{}, + } +} + +func TestDS1_Act(t *testing.T) { + ds1 := exampleDS1() + + if ds1.Act() != int(ds1.act) { + t.Error("unexpected value in example ds1") + } +} + +func TestDS1_AddFile(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.files) + + ds1.AddFile("other.ds1") + + numAfter := len(ds1.files) + + if (numBefore+1) != numAfter { + t.Error("unexpected number of files in ds1") + } +} + +func TestDS1_AddObject(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.objects) + + ds1.AddObject(Object{}) + + numAfter := len(ds1.objects) + + if (numBefore+1) != numAfter { + t.Error("unexpected number of objects in ds1") + } +} + +func TestDS1_Files(t *testing.T) { + ds1 := exampleDS1() + + files := ds1.Files() + + for idx := range files { + if ds1.files[idx] != files[idx] { + t.Error("unexpected files from ds1") + } + } +} + +func TestDS1_Height(t *testing.T) { + ds1 := exampleDS1() + + if int(ds1.height) != ds1.Height(){ + t.Error("unexpected height") + } +} + +func TestDS1_Marshal(t *testing.T) { + a := exampleDS1() + + bytes := a.Marshal() + + b, err := LoadDS1(bytes) + if err != nil { + t.Error("could not load new ds1 from marshalled ds1 data") + return + } + + if b.width != a.width { + t.Error("new ds1 does not match original") + } +} + +func TestDS1_NumberOfFloors(t *testing.T) { + ds1 := exampleDS1() + + if ds1.NumberOfFloorLayers() != int(ds1.numberOfFloorLayers) { + t.Error("unexpected number of floor layers") + } +} + +func TestDS1_NumberOfShadowLayers(t *testing.T) { + ds1 := exampleDS1() + + if ds1.NumberOfShadowLayers() != int(ds1.numberOfShadowLayers) { + t.Error("unexpected number of shadow layers") + } +} + +func TestDS1_NumberOfSubstitutionLayers(t *testing.T) { + ds1 := exampleDS1() + + if ds1.NumberOfSubstitutionLayers() != int(ds1.numberOfSubstitutionLayers) { + t.Error("unexpected number of substitution layers") + } +} + +func TestDS1_NumberOfWalls(t *testing.T) { + ds1 := exampleDS1() + + if ds1.NumberOfWallLayers() != int(ds1.numberOfWallLayers) { + t.Error("unexpected number of wall layers") + } +} + +func TestDS1_Objects(t *testing.T) { + ds1 := exampleDS1() + + objects := ds1.Objects() + + for idx := range ds1.objects { + if !ds1.objects[idx].Equals(&objects[idx]) { + t.Error("unexpected object") + } + } +} + +func TestDS1_RemoveFile(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.files) + + ds1.RemoveFile("nonexistant file") + + if len(ds1.files) != numBefore { + t.Error("file removed when it should not have been") + } + + filename := "c.ds1" + + ds1.AddFile(filename) + + if len(ds1.files) == numBefore { + t.Error("file not added when it should have been") + } + + ds1.RemoveFile(filename) + + if len(ds1.files) != numBefore { + t.Error("file not removed when it should have been") + } +} + +func TestDS1_RemoveObject(t *testing.T) { + ds1 := exampleDS1() + + nice := 69420 + + obj := Object{ + ID: nice, + } + + ds1.AddObject(obj) + + numBefore := len(ds1.objects) + + ds1.RemoveObject(obj) + + if len(ds1.objects) == numBefore { + t.Error("did not remove object when expected") + } +} + +func TestDS1_SetAct(t *testing.T) { + ds1 := exampleDS1() + + ds1.SetAct(-1) + + if ds1.Act() < 0 { + t.Error("act cannot be less than 0") + } + + nice := 69420 + + ds1.SetAct(nice) + + if int(ds1.act) != nice { + t.Error("unexpected value for act") + } +} + +func TestDS1_SetHeight(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetSize(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetSubstitutionGroups(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetSubstitutionType(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetTile(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetTiles(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetVersion(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SetWidth(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_Size(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SubstitutionGroups(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_SubstitutionType(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_Tile(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_Tiles(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_Version(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestDS1_Width(t *testing.T) { + //ds1 := exampleDS1() +} + +func TestLoadDS1(t *testing.T) { + //ds1 := exampleDS1() +} diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go index ae8cc427..f478d9f5 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go @@ -30,8 +30,8 @@ const ( hiddenLength = 1 ) -// FloorShadowRecord represents a floor or shadow record in a DS1 file. -type FloorShadowRecord struct { +// FloorShadow represents a floor or shadow record in a DS1 file (they share a common struct). +type FloorShadow struct { Prop1 byte Sequence byte Unknown1 byte @@ -43,13 +43,19 @@ type FloorShadowRecord struct { YAdjust int } +// Floor represents a floor record in a DS1 file. (it is just an alias of FloorShadow). +type Floor = FloorShadow + +// Shadow represents a shadow record in a DS1 file. (it is just an alias of FloorShadow). +type Shadow = FloorShadow + // Hidden returns if floor/shadow is hidden -func (f *FloorShadowRecord) Hidden() bool { +func (f *Floor) Hidden() bool { return f.HiddenBytes > 0 } // Decode decodes floor-shadow record -func (f *FloorShadowRecord) Decode(dw uint32) { +func (f *Floor) Decode(dw uint32) { f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) @@ -59,7 +65,7 @@ func (f *FloorShadowRecord) Decode(dw uint32) { } // Encode adds Floor's bits to stream writter given -func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) { +func (f *Floor) Encode(sw *d2datautils.StreamWriter) { sw.PushBits32(uint32(f.Prop1), prop1Length) sw.PushBits32(uint32(f.Sequence), sequenceLength) sw.PushBits32(uint32(f.Unknown1), unknown1Length) diff --git a/d2common/d2fileformats/d2ds1/object.go b/d2common/d2fileformats/d2ds1/object.go index 6a47164a..5173994d 100644 --- a/d2common/d2fileformats/d2ds1/object.go +++ b/d2common/d2fileformats/d2ds1/object.go @@ -13,3 +13,13 @@ type Object struct { Flags int Paths []d2path.Path } + +// Equals checks if this Object is equivalent to the given Object +func (o *Object) Equals(other *Object) bool { + return o.Type == other.Type && + o.ID == other.ID && + o.X == other.X && + o.Y == other.Y && + o.Flags == other.Flags && + len(o.Paths) == len(other.Paths) +} diff --git a/d2common/d2fileformats/d2ds1/substitution_record.go b/d2common/d2fileformats/d2ds1/substitution_record.go index fa379e27..0c5d8542 100644 --- a/d2common/d2fileformats/d2ds1/substitution_record.go +++ b/d2common/d2fileformats/d2ds1/substitution_record.go @@ -1,6 +1,6 @@ package d2ds1 -// SubstitutionRecord represents a substitution record in a DS1 file. -type SubstitutionRecord struct { +// Substitution represents a substitution record in a DS1 file. +type Substitution struct { Unknown uint32 } diff --git a/d2common/d2fileformats/d2ds1/tile_record.go b/d2common/d2fileformats/d2ds1/tile_record.go index 1e560f92..611ae49d 100644 --- a/d2common/d2fileformats/d2ds1/tile_record.go +++ b/d2common/d2fileformats/d2ds1/tile_record.go @@ -1,9 +1,18 @@ package d2ds1 -// TileRecord represents a tile record in a DS1 file. -type TileRecord struct { - Floors []FloorShadowRecord // Collection of floor records - Walls []WallRecord // Collection of wall records - Shadows []FloorShadowRecord // Collection of shadow records - Substitutions []SubstitutionRecord // Collection of substitutions +// Tile represents a tile record in a DS1 file. +type Tile struct { + Floors []Floor // Collection of floor records + Walls []Wall // Collection of wall records + Shadows []Shadow // Collection of shadow records + Substitutions []Substitution // Collection of substitutions +} + +func makeDefaultTile() Tile { + return Tile{ + Floors: []Floor{{}}, + Walls: []Wall{{}}, + Shadows: []Shadow{{}}, + Substitutions: []Substitution{{}}, + } } diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall_record.go index 51f2241d..5123e9f7 100644 --- a/d2common/d2fileformats/d2ds1/wall_record.go +++ b/d2common/d2fileformats/d2ds1/wall_record.go @@ -5,8 +5,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) -// WallRecord represents a wall record. -type WallRecord struct { +// Wall represents a wall record. +type Wall struct { Type d2enum.TileType Zero byte Prop1 byte @@ -20,12 +20,12 @@ type WallRecord struct { } // Hidden returns if wall is hidden -func (w *WallRecord) Hidden() bool { +func (w *Wall) Hidden() bool { return w.HiddenBytes > 0 } // Decode decodes wall record -func (w *WallRecord) Decode(dw uint32) { +func (w *Wall) Decode(dw uint32) { w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) @@ -35,7 +35,7 @@ func (w *WallRecord) Decode(dw uint32) { } // Encode adds wall's record's bytes into stream writer given -func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) { +func (w *Wall) Encode(sw *d2datautils.StreamWriter) { sw.PushBits32(uint32(w.Prop1), prop1Length) sw.PushBits32(uint32(w.Sequence), sequenceLength) sw.PushBits32(uint32(w.Unknown1), unknown1Length) diff --git a/d2core/d2config/defaults.go b/d2core/d2config/defaults.go index 7e8a539b..65e9b588 100644 --- a/d2core/d2config/defaults.go +++ b/d2core/d2config/defaults.go @@ -20,7 +20,7 @@ func DefaultConfig() *Configuration { VsyncEnabled: true, SfxVolume: defaultSfxVolume, BgmVolume: defaultBgmVolume, - MpqPath: "C:/Program Files (x86)/Diablo II", + MpqPath: "C:/Program files (x86)/Diablo II", Backend: "Ebiten", MpqLoadOrder: []string{ "Patch_D2.mpq", @@ -41,7 +41,7 @@ func DefaultConfig() *Configuration { switch runtime.GOOS { case "windows": if runtime.GOARCH == "386" { - config.MpqPath = "C:/Program Files/Diablo II" + config.MpqPath = "C:/Program files/Diablo II" } case "darwin": config.MpqPath = "/Applications/Diablo II/" @@ -60,7 +60,7 @@ func DefaultConfig() *Configuration { } case "linux": if usr, err := user.Current(); err == nil { - config.MpqPath = path.Join(usr.HomeDir, ".wine/drive_c/Program Files (x86)/Diablo II") + config.MpqPath = path.Join(usr.HomeDir, ".wine/drive_c/Program files (x86)/Diablo II") } } diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 981cbd4c..483ae862 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -118,8 +118,8 @@ func (m *MapEngine) AddDS1(fileName string) { m.Error(err.Error()) } - for idx := range ds1.Files { - dt1File := ds1.Files[idx] + for idx := range ds1.files { + dt1File := ds1.files[idx] dt1File = strings.ToLower(dt1File) dt1File = strings.ReplaceAll(dt1File, "c:", "") // Yes they did... dt1File = strings.ReplaceAll(dt1File, ".tg1", ".dt1") // Yes they did... diff --git a/d2core/d2records/level_presets_loader.go b/d2core/d2records/level_presets_loader.go index 39847d6b..ee8bf6fd 100644 --- a/d2core/d2records/level_presets_loader.go +++ b/d2core/d2records/level_presets_loader.go @@ -25,7 +25,7 @@ func levelPresetLoader(r *RecordManager, d *d2txt.DataDictionary) error { Scan: d.Number("Scan") == 1, Pops: d.Number("Pops"), PopPad: d.Number("PopPad"), - FileCount: d.Number("Files"), + FileCount: d.Number("files"), Files: [6]string{ d.String("File1"), d.String("File2"), diff --git a/utils/extract-mpq/doc.go b/utils/extract-mpq/doc.go index 9f8eb89b..d1c1bac0 100644 --- a/utils/extract-mpq/doc.go +++ b/utils/extract-mpq/doc.go @@ -6,7 +6,7 @@ // // Usage: // First run `go install extract-mpq.go` in this directory. -// Navigate to the Diablo II directory (ex: C:/Program Files (x86)/Diablo II) +// Navigate to the Diablo II directory (ex: C:/Program files (x86)/Diablo II) // then run extract-mpq(.exe) with the filename of the mpq to be extracted. // // extract-mpq d2char.mpq From 99908016bef052c659bc370067523166a93172d8 Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:04:44 +0100 Subject: [PATCH 2/9] 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 --- d2common/d2fileformats/d2ds1/ds1.go | 23 +- d2common/d2fileformats/d2ds1/ds1_test.go | 545 ++++++++++++------ .../d2ds1/{tile_record.go => tile.go} | 0 d2core/d2map/d2mapengine/engine.go | 4 +- d2core/d2map/d2mapengine/map_tile.go | 2 +- d2core/d2map/d2mapgen/act1_overworld.go | 2 +- d2core/d2map/d2maprenderer/renderer.go | 6 +- d2core/d2map/d2maprenderer/tile_cache.go | 6 +- d2core/d2map/d2mapstamp/stamp.go | 12 +- 9 files changed, 404 insertions(+), 196 deletions(-) rename d2common/d2fileformats/d2ds1/{tile_record.go => tile.go} (100%) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index ddcadec3..21d198f1 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -1,6 +1,8 @@ package d2ds1 import ( + "fmt" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" @@ -61,7 +63,8 @@ type DS1 struct { npcIndexes []int } -// Files returns a list of file path strings. These correspond to DT1 paths that this DS1 will use. +// Files returns a list of file path strings. +// These correspond to DT1 paths that this DS1 will use. func (ds1 *DS1) Files() []string { return ds1.files } @@ -76,13 +79,16 @@ func (ds1 *DS1) AddFile(file string) { } // RemoveFile removes a file from the files slice -func (ds1 *DS1) RemoveFile(file string) { +func (ds1 *DS1) RemoveFile(file string) error { for idx := range ds1.files { if ds1.files[idx] == file { ds1.files = append(ds1.files[:idx], ds1.files[idx+1:]...) - break + + return nil } } + + return fmt.Errorf("file %s not found", file) } // Objects returns the slice of objects found in this ds1 @@ -209,6 +215,8 @@ func (ds1 *DS1) SetWidth(w int) { ds1.tiles[rowIdx] = append(ds1.tiles[rowIdx], newTiles...) } } + + ds1.width = int32(w) } // Height returns te ds1's height @@ -246,6 +254,8 @@ func (ds1 *DS1) SetHeight(h int) { newRows[rowIdx][colIdx] = makeDefaultTile() } } + + ds1.tiles = append(ds1.tiles, newRows...) } // if the ds1 has too many rows @@ -267,10 +277,11 @@ func (ds1 *DS1) Size() (w, h int) { return int(ds1.width), int(ds1.height) } -// SetSize sets the ds1's size (width,height) -func (ds1 *DS1) SetSize(w, h int) { +// setSize force sets the ds1's size (width,height) +func (ds1 *DS1) setSize(w, h int) { ds1.SetWidth(w) ds1.SetHeight(h) + ds1.width, ds1.height = int32(w), int32(h) } // Act returns the ds1's act @@ -348,7 +359,7 @@ func (ds1 *DS1) update() { ds1.enforceAllTileLayersMatch() ds1.updateLayerCounts() - ds1.SetSize(len(ds1.tiles[0]), len(ds1.tiles)) + ds1.setSize(len(ds1.tiles[0]), len(ds1.tiles)) ds1.dirty = false } diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 9152e70a..d5aac720 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -1,8 +1,11 @@ package d2ds1 import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "fmt" + "testing" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) func exampleDS1() *DS1 { @@ -34,49 +37,39 @@ func exampleDS1() *DS1 { numberOfFloorLayers: 1, numberOfShadowLayers: 1, numberOfSubstitutionLayers: 1, - layerStreamTypes: []d2enum.LayerStreamType{ + layerStreamTypes: []d2enum.LayerStreamType{ d2enum.LayerStreamWall1, d2enum.LayerStreamOrientation1, d2enum.LayerStreamFloor1, d2enum.LayerStreamShadow, }, - npcIndexes: []int{}, + npcIndexes: []int{}, } } -func TestDS1_Act(t *testing.T) { - ds1 := exampleDS1() +// checks, if DS1 structure could be marshaled and unmarshaled +func testIfRestorable(ds1 *DS1) error { + var err error - if ds1.Act() != int(ds1.act) { - t.Error("unexpected value in example ds1") - } + data := ds1.Marshal() + _, err = LoadDS1(data) + + return err } -func TestDS1_AddFile(t *testing.T) { - ds1 := exampleDS1() +func TestDS1_Marshal(t *testing.T) { + a := exampleDS1() - numBefore := len(ds1.files) + bytes := a.Marshal() - ds1.AddFile("other.ds1") - - numAfter := len(ds1.files) - - if (numBefore+1) != numAfter { - t.Error("unexpected number of files in ds1") + b, err := LoadDS1(bytes) + if err != nil { + t.Error("could not load new ds1 from marshaled ds1 data") + return } -} -func TestDS1_AddObject(t *testing.T) { - ds1 := exampleDS1() - - numBefore := len(ds1.objects) - - ds1.AddObject(Object{}) - - numAfter := len(ds1.objects) - - if (numBefore+1) != numAfter { - t.Error("unexpected number of objects in ds1") + if b.width != a.width { + t.Error("new ds1 does not match original") } } @@ -92,27 +85,347 @@ func TestDS1_Files(t *testing.T) { } } +func TestDS1_AddFile(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.files) + + ds1.AddFile("other.ds1") + + numAfter := len(ds1.files) + + if (numBefore + 1) != numAfter { + t.Error("unexpected number of files in ds1") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_RemoveFile(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.files) + + err := ds1.RemoveFile("nonexistant file") + if err == nil { + t.Fatal("file 'nonexistant file' doesn't exist but ds1.RemoveFile doesn't return error") + } + + if len(ds1.files) != numBefore { + t.Error("file removed when it should not have been") + } + + filename := "c.ds1" + + ds1.AddFile(filename) + + if len(ds1.files) == numBefore { + t.Error("file not added when it should have been") + } + + err = ds1.RemoveFile(filename) + if err != nil { + t.Error(err) + } + + if len(ds1.files) != numBefore { + t.Error("file not removed when it should have been") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_Objects(t *testing.T) { + ds1 := exampleDS1() + + objects := ds1.Objects() + + for idx := range ds1.objects { + if !ds1.objects[idx].Equals(&objects[idx]) { + t.Error("unexpected object") + } + } +} + +func TestDS1_AddObject(t *testing.T) { + ds1 := exampleDS1() + + numBefore := len(ds1.objects) + + ds1.AddObject(Object{}) + + numAfter := len(ds1.objects) + + if (numBefore + 1) != numAfter { + t.Error("unexpected number of objects in ds1") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_RemoveObject(t *testing.T) { + ds1 := exampleDS1() + + nice := 69420 + + obj := Object{ + ID: nice, + } + + ds1.AddObject(obj) + + numBefore := len(ds1.objects) + + ds1.RemoveObject(obj) + + if len(ds1.objects) == numBefore { + t.Error("did not remove object when expected") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_Tiles(t *testing.T) { + ds1 := exampleDS1() + + tiles := ds1.Tiles() + + for y := range tiles { + for x := range tiles[y] { + if len(ds1.tiles[y][x].Floors) != len(tiles[y][x].Floors) { + t.Fatal("number of tile's floors returned by ds1.Tiles() isn't same as real number") + } + + if ds1.tiles[y][x].Walls[0] != tiles[y][x].Walls[0] { + t.Fatal("wall record returned by ds1.Tiles isn't equal to real") + } + } + } +} + +func TestDS1_SetTiles(t *testing.T) { + ds1 := exampleDS1() + + exampleTile1 := Tile{ + Floors: []FloorShadow{ + {0, 0, 2, 3, 4, 55, 33, true, 999}, + }, + Shadows: []FloorShadow{ + {2, 4, 5, 33, 6, 7, 0, false, 1024}, + }, + } + + exampleTile2 := Tile{ + Walls: []Wall{ + {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, + }, + Shadows: []FloorShadow{ + {2, 4, 5, 33, 6, 7, 0, false, 1024}, + }, + } + + tiles := [][]Tile{{exampleTile1, exampleTile2}} + + ds1.SetTiles(tiles) + + if ds1.tiles[0][0].Floors[0] != exampleTile1.Floors[0] { + t.Fatal("unexpected tile was set") + } + + if len(ds1.tiles[0][0].Walls) != len(exampleTile1.Walls) { + t.Fatal("unexpected tile was set") + } + + if ds1.tiles[0][1].Walls[0] != exampleTile2.Walls[0] { + t.Fatal("unexpected tile was set") + } + + if len(ds1.tiles[0][1].Walls) != len(exampleTile2.Walls) { + t.Fatal("unexpected tile was set") + } +} + +func TestDS1_Tile(t *testing.T) { + ds1 := exampleDS1() + + x, y := 1, 0 + + if ds1.tiles[y][x].Floors[0] != ds1.Tile(x, y).Floors[0] { + t.Fatal("ds1.Tile returned invalid value") + } +} + +func TestDS1_SetTile(t *testing.T) { + ds1 := exampleDS1() + + exampleTile := Tile{ + Floors: []FloorShadow{ + {5, 8, 9, 4, 3, 4, 2, true, 1024}, + {8, 22, 7, 9, 6, 3, 0, false, 1024}, + }, + Walls: []Wall{ + {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, + }, + Shadows: []FloorShadow{ + {2, 44, 99, 2, 4, 3, 2, true, 933}, + }, + } + + ds1.SetTile(0, 0, &exampleTile) + + if ds1.tiles[0][0].Floors[0] != exampleTile.Floors[0] { + t.Fatal("unexpected tile was set") + } + + if len(ds1.tiles[0][0].Walls) != len(exampleTile.Walls) { + t.Fatal("unexpected tile was set") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_Version(t *testing.T) { + ds1 := exampleDS1() + + version := ds1.version + + if version != int32(ds1.Version()) { + t.Fatal("version returned by ds1.Version() and real aren't equal") + } +} + +func TestDS1_SetVersion(t *testing.T) { + ds1 := exampleDS1() + + newVersion := 8 + + ds1.SetVersion(newVersion) + + if newVersion != int(ds1.version) { + t.Fatal("ds1.SetVersion set version incorrectly") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_Width(t *testing.T) { + ds1 := exampleDS1() + + if int(ds1.width) != ds1.Width() { + t.Error("unexpected width") + } +} + +func TestDS1_SetWidth(t *testing.T) { + ds1 := exampleDS1() + + var newWidth int32 = 4 + + ds1.SetWidth(int(newWidth)) + + if newWidth != ds1.width { + t.Fatal("unexpected width after set") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + func TestDS1_Height(t *testing.T) { ds1 := exampleDS1() - if int(ds1.height) != ds1.Height(){ + if int(ds1.height) != ds1.Height() { t.Error("unexpected height") } } -func TestDS1_Marshal(t *testing.T) { - a := exampleDS1() +func TestDS1_SetHeight(t *testing.T) { + ds1 := exampleDS1() - bytes := a.Marshal() + var newHeight int32 = 5 - b, err := LoadDS1(bytes) - if err != nil { - t.Error("could not load new ds1 from marshalled ds1 data") - return + ds1.SetHeight(int(newHeight)) + + if newHeight != ds1.height { + fmt.Println(newHeight, ds1.height) + t.Fatal("unexpected heigth after set") } - if b.width != a.width { - t.Error("new ds1 does not match original") + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_Act(t *testing.T) { + ds1 := exampleDS1() + + if ds1.Act() != int(ds1.act) { + t.Error("unexpected value in example ds1") + } +} + +func TestDS1_SetAct(t *testing.T) { + ds1 := exampleDS1() + + ds1.SetAct(-1) + + if ds1.Act() < 0 { + t.Error("act cannot be less than 0") + } + + nice := 69420 + + ds1.SetAct(nice) + + if int(ds1.act) != nice { + t.Error("unexpected value for act") + } + + if err := testIfRestorable(ds1); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + +func TestDS1_SubstitutionType(t *testing.T) { + ds1 := exampleDS1() + + st := ds1.substitutionType + + if int(st) != ds1.SubstitutionType() { + t.Fatal("unexpected substitution type returned") + } +} + +func TestDS1_SetSubstitutionType(t *testing.T) { + ds1 := exampleDS1() + + newST := 5 + + ds1.SetSubstitutionType(newST) + + if ds1.substitutionType != int32(newST) { + t.Fatal("unexpected substitutionType was set") + } +} + +func TestDS1_NumberOfWalls(t *testing.T) { + ds1 := exampleDS1() + + if ds1.NumberOfWallLayers() != int(ds1.numberOfWallLayers) { + t.Error("unexpected number of wall layers") } } @@ -140,150 +453,34 @@ func TestDS1_NumberOfSubstitutionLayers(t *testing.T) { } } -func TestDS1_NumberOfWalls(t *testing.T) { +func TestDS1_SubstitutionGroups(t *testing.T) { ds1 := exampleDS1() - if ds1.NumberOfWallLayers() != int(ds1.numberOfWallLayers) { - t.Error("unexpected number of wall layers") - } -} + sg := ds1.SubstitutionGroups() -func TestDS1_Objects(t *testing.T) { - ds1 := exampleDS1() - - objects := ds1.Objects() - - for idx := range ds1.objects { - if !ds1.objects[idx].Equals(&objects[idx]) { - t.Error("unexpected object") + for i := 0; i < len(ds1.substitutionGroups); i++ { + if sg[i] != ds1.substitutionGroups[i] { + t.Fatal("unexpected substitution group returned") } } } -func TestDS1_RemoveFile(t *testing.T) { - ds1 := exampleDS1() - - numBefore := len(ds1.files) - - ds1.RemoveFile("nonexistant file") - - if len(ds1.files) != numBefore { - t.Error("file removed when it should not have been") - } - - filename := "c.ds1" - - ds1.AddFile(filename) - - if len(ds1.files) == numBefore { - t.Error("file not added when it should have been") - } - - ds1.RemoveFile(filename) - - if len(ds1.files) != numBefore { - t.Error("file not removed when it should have been") - } -} - -func TestDS1_RemoveObject(t *testing.T) { - ds1 := exampleDS1() - - nice := 69420 - - obj := Object{ - ID: nice, - } - - ds1.AddObject(obj) - - numBefore := len(ds1.objects) - - ds1.RemoveObject(obj) - - if len(ds1.objects) == numBefore { - t.Error("did not remove object when expected") - } -} - -func TestDS1_SetAct(t *testing.T) { - ds1 := exampleDS1() - - ds1.SetAct(-1) - - if ds1.Act() < 0 { - t.Error("act cannot be less than 0") - } - - nice := 69420 - - ds1.SetAct(nice) - - if int(ds1.act) != nice { - t.Error("unexpected value for act") - } -} - -func TestDS1_SetHeight(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_SetSize(t *testing.T) { - //ds1 := exampleDS1() -} - func TestDS1_SetSubstitutionGroups(t *testing.T) { - //ds1 := exampleDS1() -} + ds1 := exampleDS1() -func TestDS1_SetSubstitutionType(t *testing.T) { - //ds1 := exampleDS1() -} + newGroup := []SubstitutionGroup{ + { + TileX: 20, + TileY: 12, + WidthInTiles: 212, + HeightInTiles: 334, + Unknown: 1024, + }, + } -func TestDS1_SetTile(t *testing.T) { - //ds1 := exampleDS1() -} + ds1.SetSubstitutionGroups(newGroup) -func TestDS1_SetTiles(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_SetVersion(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_SetWidth(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_Size(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_SubstitutionGroups(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_SubstitutionType(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_Tile(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_Tiles(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_Version(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestDS1_Width(t *testing.T) { - //ds1 := exampleDS1() -} - -func TestLoadDS1(t *testing.T) { - //ds1 := exampleDS1() + if ds1.substitutionGroups[0] != newGroup[0] { + t.Fatal("unexpected substitution group added") + } } diff --git a/d2common/d2fileformats/d2ds1/tile_record.go b/d2common/d2fileformats/d2ds1/tile.go similarity index 100% rename from d2common/d2fileformats/d2ds1/tile_record.go rename to d2common/d2fileformats/d2ds1/tile.go diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 483ae862..0fc01256 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -118,8 +118,8 @@ func (m *MapEngine) AddDS1(fileName string) { m.Error(err.Error()) } - for idx := range ds1.files { - dt1File := ds1.files[idx] + for idx := range ds1.Files() { + dt1File := ds1.Files()[idx] dt1File = strings.ToLower(dt1File) dt1File = strings.ReplaceAll(dt1File, "c:", "") // Yes they did... dt1File = strings.ReplaceAll(dt1File, ".tg1", ".dt1") // Yes they did... diff --git a/d2core/d2map/d2mapengine/map_tile.go b/d2core/d2map/d2mapengine/map_tile.go index 4ed17333..e01bb0e5 100644 --- a/d2core/d2map/d2mapengine/map_tile.go +++ b/d2core/d2map/d2mapengine/map_tile.go @@ -8,7 +8,7 @@ import ( // MapTile is a tile placed on the map type MapTile struct { - Components d2ds1.TileRecord + Components d2ds1.Tile RegionType d2enum.RegionIdType SubTiles [25]d2dt1.SubTileFlags } diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index 58626065..44e6e4bb 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -284,7 +284,7 @@ func (g *MapGenerator) generateWilderness1Contents(rect d2geom.Rectangle) { for x := 0; x < rect.Width; x++ { tile := g.engine.Tile(rect.Left+x, rect.Top+y) tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType) - tile.Components.Floors = []d2ds1.FloorShadowRecord{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass + tile.Components.Floors = []d2ds1.FloorShadow{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass tile.PrepareTile(x, y, g.engine) } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index e7fb8b67..7c7e9802 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -393,7 +393,7 @@ func (mr *MapRenderer) renderTilePass3(tile *d2mapengine.MapTile, target d2inter } } -func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interface.Surface) { +func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadow, target d2interface.Surface) { var img d2interface.Surface if !tile.Animated { img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) @@ -415,7 +415,7 @@ func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interf target.Render(img) } -func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2interface.Surface) { +func (mr *MapRenderer) renderWall(tile d2ds1.Wall, viewport *Viewport, target d2interface.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) if img == nil { mr.Warningf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type) @@ -431,7 +431,7 @@ func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, tar target.Render(img) } -func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2interface.Surface) { +func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadow, target d2interface.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if img == nil { mr.Warningf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index 0503f732..ee53ce7b 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -53,7 +53,7 @@ func (mr *MapRenderer) generateTileCache() { } } -func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) { +func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadow) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), 0) var tileData []*d2dt1.Tile @@ -110,7 +110,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) { } } -func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) { +func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadow) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), d2enum.TileShadow) var tileData *d2dt1.Tile @@ -153,7 +153,7 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) { mr.setImageCacheRecord(tile.Style, tile.Sequence, d2enum.TileShadow, tile.RandomIndex, image) } -func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) { +func (mr *MapRenderer) generateWallCache(tile *d2ds1.Wall) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), tile.Type) var tileData *d2dt1.Tile diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index 31e16c07..ec1ef92a 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -31,7 +31,7 @@ type Stamp struct { // Size returns the size of the stamp in tiles. func (mr *Stamp) Size() d2geom.Size { - return d2geom.Size{Width: int(mr.ds1.Width), Height: int(mr.ds1.Height)} + return d2geom.Size{Width: mr.ds1.Width(), Height: mr.ds1.Height()} } // LevelPreset returns the level preset ID. @@ -55,8 +55,8 @@ func (mr *Stamp) RegionPath() string { } // Tile returns the tile at the given x and y tile coordinates. -func (mr *Stamp) Tile(x, y int) *d2ds1.TileRecord { - return &mr.ds1.Tiles[y][x] +func (mr *Stamp) Tile(x, y int) *d2ds1.Tile { + return mr.ds1.Tile(x, y) } // TileData returns the tile data for the tile with given style, sequence and type. @@ -75,9 +75,9 @@ func (mr *Stamp) TileData(style, sequence int32, tileType d2enum.TileType) *d2dt func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity { entities := make([]d2interface.MapEntity, 0) - for _, object := range mr.ds1.Objects { + for _, object := range mr.ds1.Objects() { if object.Type == int(d2enum.ObjectTypeCharacter) { - monPreset := mr.factory.asset.Records.Monster.Presets[mr.ds1.Act][object.ID] + monPreset := mr.factory.asset.Records.Monster.Presets[int32(mr.ds1.Act())][object.ID] monstat := mr.factory.asset.Records.Monster.Stats[monPreset] // If monstat is nil here it is a place_ type object, idk how to handle those yet. // (See monpreset and monplace txts for reference) @@ -97,7 +97,7 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity if object.Type == int(d2enum.ObjectTypeItem) { // For objects the DS1 ID to objectID is hardcoded in the game // use the lookup table - lookup := mr.factory.asset.Records.LookupObject(int(mr.ds1.Act), object.Type, object.ID) + lookup := mr.factory.asset.Records.LookupObject(mr.ds1.Act(), object.Type, object.ID) if lookup == nil { continue From 09bbcf0b4da0f863ec5d50b188c8b5554c1fa743 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:15:46 -0800 Subject: [PATCH 3/9] renamed some files in d2ds1 --- .../d2ds1/{floor_shadow_record.go => floor_shadow.go} | 0 .../d2ds1/{substitution_record.go => substitution.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename d2common/d2fileformats/d2ds1/{floor_shadow_record.go => floor_shadow.go} (100%) rename d2common/d2fileformats/d2ds1/{substitution_record.go => substitution.go} (100%) diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow.go similarity index 100% rename from d2common/d2fileformats/d2ds1/floor_shadow_record.go rename to d2common/d2fileformats/d2ds1/floor_shadow.go diff --git a/d2common/d2fileformats/d2ds1/substitution_record.go b/d2common/d2fileformats/d2ds1/substitution.go similarity index 100% rename from d2common/d2fileformats/d2ds1/substitution_record.go rename to d2common/d2fileformats/d2ds1/substitution.go From 84d510fe168e92f32d5cb1b5a79edbbae10f2420 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:16:10 -0800 Subject: [PATCH 4/9] d2ds1.FloorShadow is now private --- d2common/d2fileformats/d2ds1/ds1_test.go | 10 +++++----- d2common/d2fileformats/d2ds1/floor_shadow.go | 11 +++++------ d2core/d2map/d2mapgen/act1_overworld.go | 2 +- d2core/d2map/d2maprenderer/renderer.go | 4 ++-- d2core/d2map/d2maprenderer/tile_cache.go | 4 ++-- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index d5aac720..96ddc16f 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -215,10 +215,10 @@ func TestDS1_SetTiles(t *testing.T) { ds1 := exampleDS1() exampleTile1 := Tile{ - Floors: []FloorShadow{ + Floors: []floorShadow{ {0, 0, 2, 3, 4, 55, 33, true, 999}, }, - Shadows: []FloorShadow{ + Shadows: []floorShadow{ {2, 4, 5, 33, 6, 7, 0, false, 1024}, }, } @@ -227,7 +227,7 @@ func TestDS1_SetTiles(t *testing.T) { Walls: []Wall{ {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, }, - Shadows: []FloorShadow{ + Shadows: []floorShadow{ {2, 4, 5, 33, 6, 7, 0, false, 1024}, }, } @@ -267,14 +267,14 @@ func TestDS1_SetTile(t *testing.T) { ds1 := exampleDS1() exampleTile := Tile{ - Floors: []FloorShadow{ + Floors: []floorShadow{ {5, 8, 9, 4, 3, 4, 2, true, 1024}, {8, 22, 7, 9, 6, 3, 0, false, 1024}, }, Walls: []Wall{ {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, }, - Shadows: []FloorShadow{ + Shadows: []floorShadow{ {2, 44, 99, 2, 4, 3, 2, true, 933}, }, } diff --git a/d2common/d2fileformats/d2ds1/floor_shadow.go b/d2common/d2fileformats/d2ds1/floor_shadow.go index f478d9f5..b8856a5f 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow.go @@ -30,8 +30,7 @@ const ( hiddenLength = 1 ) -// FloorShadow represents a floor or shadow record in a DS1 file (they share a common struct). -type FloorShadow struct { +type floorShadow struct { Prop1 byte Sequence byte Unknown1 byte @@ -43,11 +42,11 @@ type FloorShadow struct { YAdjust int } -// Floor represents a floor record in a DS1 file. (it is just an alias of FloorShadow). -type Floor = FloorShadow +// Floor represents a floor record in a DS1 file. (it is just an alias of floorShadow). +type Floor = floorShadow -// Shadow represents a shadow record in a DS1 file. (it is just an alias of FloorShadow). -type Shadow = FloorShadow +// Shadow represents a shadow record in a DS1 file. (it is just an alias of floorShadow). +type Shadow = floorShadow // Hidden returns if floor/shadow is hidden func (f *Floor) Hidden() bool { diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index 44e6e4bb..e1d78176 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -284,7 +284,7 @@ func (g *MapGenerator) generateWilderness1Contents(rect d2geom.Rectangle) { for x := 0; x < rect.Width; x++ { tile := g.engine.Tile(rect.Left+x, rect.Top+y) tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType) - tile.Components.Floors = []d2ds1.FloorShadow{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass + tile.Components.Floors = []d2ds1.Floor{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass tile.PrepareTile(x, y, g.engine) } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 7c7e9802..362aab8a 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -393,7 +393,7 @@ func (mr *MapRenderer) renderTilePass3(tile *d2mapengine.MapTile, target d2inter } } -func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadow, target d2interface.Surface) { +func (mr *MapRenderer) renderFloor(tile d2ds1.Floor, target d2interface.Surface) { var img d2interface.Surface if !tile.Animated { img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) @@ -431,7 +431,7 @@ func (mr *MapRenderer) renderWall(tile d2ds1.Wall, viewport *Viewport, target d2 target.Render(img) } -func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadow, target d2interface.Surface) { +func (mr *MapRenderer) renderShadow(tile d2ds1.Shadow, target d2interface.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if img == nil { mr.Warningf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index ee53ce7b..7ac05191 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -53,7 +53,7 @@ func (mr *MapRenderer) generateTileCache() { } } -func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadow) { +func (mr *MapRenderer) generateFloorCache(tile *d2ds1.Floor) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), 0) var tileData []*d2dt1.Tile @@ -110,7 +110,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadow) { } } -func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadow) { +func (mr *MapRenderer) generateShadowCache(tile *d2ds1.Shadow) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), d2enum.TileShadow) var tileData *d2dt1.Tile From 8a5148647b149cc85266e1a168aa3f2874f97484 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:23:49 -0800 Subject: [PATCH 5/9] renamed another file --- d2common/d2fileformats/d2ds1/{wall_record.go => wall.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename d2common/d2fileformats/d2ds1/{wall_record.go => wall.go} (100%) diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall.go similarity index 100% rename from d2common/d2fileformats/d2ds1/wall_record.go rename to d2common/d2fileformats/d2ds1/wall.go From 169521e546ec0367a4fbeca6d068bafdacf7e335 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:57:38 -0800 Subject: [PATCH 6/9] DS1.Tile() now calls update if dirty --- d2common/d2fileformats/d2ds1/ds1.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 21d198f1..d62a65d7 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -140,6 +140,10 @@ func (ds1 *DS1) SetTiles(tiles [][]Tile) { // Tile returns the tile at the given x,y tile coordinate (nil if x,y is out of bounds) func (ds1 *DS1) Tile(x, y int) *Tile { + if ds1.dirty { + ds1.update() + } + if y >= len(ds1.tiles) { return nil } From 46d2bc6559b4d13f94d9838dca3ee2cd33e1277e Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Sat, 20 Feb 2021 20:28:06 +0100 Subject: [PATCH 7/9] 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 --- d2common/d2fileformats/d2ds1/ds1.go | 2 + d2common/d2fileformats/d2ds1/ds1_test.go | 184 ++++++++++++------- d2common/d2fileformats/d2ds1/floor_shadow.go | 4 +- 3 files changed, 117 insertions(+), 73 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index d62a65d7..ff1a907c 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -135,7 +135,9 @@ func (ds1 *DS1) SetTiles(tiles [][]Tile) { } ds1.tiles = tiles + ds1.layerStreamTypes = ds1.setupStreamLayerTypes() ds1.dirty = true + ds1.update() } // Tile returns the tile at the given x,y tile coordinate (nil if x,y is out of bounds) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 96ddc16f..2d36c89b 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -1,8 +1,6 @@ package d2ds1 import ( - "fmt" - "testing" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -48,11 +46,16 @@ func exampleDS1() *DS1 { } // checks, if DS1 structure could be marshaled and unmarshaled -func testIfRestorable(ds1 *DS1) error { +func testIfRestorable(ds1 *DS1, test func(ds1 *DS1)) error { + test(ds1) + var err error data := ds1.Marshal() - _, err = LoadDS1(data) + newDS1, err := LoadDS1(data) + _ = newDS1 + + test(newDS1) return err } @@ -94,11 +97,13 @@ func TestDS1_AddFile(t *testing.T) { numAfter := len(ds1.files) - if (numBefore + 1) != numAfter { - t.Error("unexpected number of files in ds1") + test := func(ds1 *DS1) { + if (numBefore + 1) != numAfter { + t.Error("unexpected number of files in ds1") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -106,35 +111,37 @@ func TestDS1_AddFile(t *testing.T) { func TestDS1_RemoveFile(t *testing.T) { ds1 := exampleDS1() - numBefore := len(ds1.files) + test := func(ds1 *DS1) { + numBefore := len(ds1.files) - err := ds1.RemoveFile("nonexistant file") - if err == nil { - t.Fatal("file 'nonexistant file' doesn't exist but ds1.RemoveFile doesn't return error") + err := ds1.RemoveFile("nonexistant file") + if err == nil { + t.Fatal("file 'nonexistant file' doesn't exist but ds1.RemoveFile doesn't return error") + } + + if len(ds1.files) != numBefore { + t.Error("file removed when it should not have been") + } + + filename := "c.ds1" + + ds1.AddFile(filename) + + if len(ds1.files) == numBefore { + t.Error("file not added when it should have been") + } + + err = ds1.RemoveFile(filename) + if err != nil { + t.Error(err) + } + + if len(ds1.files) != numBefore { + t.Error("file not removed when it should have been") + } } - if len(ds1.files) != numBefore { - t.Error("file removed when it should not have been") - } - - filename := "c.ds1" - - ds1.AddFile(filename) - - if len(ds1.files) == numBefore { - t.Error("file not added when it should have been") - } - - err = ds1.RemoveFile(filename) - if err != nil { - t.Error(err) - } - - if len(ds1.files) != numBefore { - t.Error("file not removed when it should have been") - } - - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -160,11 +167,13 @@ func TestDS1_AddObject(t *testing.T) { numAfter := len(ds1.objects) - if (numBefore + 1) != numAfter { - t.Error("unexpected number of objects in ds1") + test := func(ds1 *DS1) { + if (numBefore + 1) != numAfter { + t.Error("unexpected number of objects in ds1") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -184,11 +193,13 @@ func TestDS1_RemoveObject(t *testing.T) { ds1.RemoveObject(obj) - if len(ds1.objects) == numBefore { - t.Error("did not remove object when expected") + test := func(ds1 *DS1) { + if len(ds1.objects) == numBefore { + t.Error("did not remove object when expected") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -218,18 +229,30 @@ func TestDS1_SetTiles(t *testing.T) { Floors: []floorShadow{ {0, 0, 2, 3, 4, 55, 33, true, 999}, }, - Shadows: []floorShadow{ - {2, 4, 5, 33, 6, 7, 0, false, 1024}, - }, - } - - exampleTile2 := Tile{ Walls: []Wall{ {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, }, Shadows: []floorShadow{ {2, 4, 5, 33, 6, 7, 0, false, 1024}, }, + Substitutions: []Substitution{ + {1024}, + }, + } + + exampleTile2 := Tile{ + Floors: []floorShadow{ + {0, 0, 2, 3, 4, 55, 33, true, 999}, + }, + Walls: []Wall{ + {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, + }, + Shadows: []floorShadow{ + {2, 4, 5, 33, 6, 7, 0, false, 1024}, + }, + Substitutions: []Substitution{ + {1234}, + }, } tiles := [][]Tile{{exampleTile1, exampleTile2}} @@ -244,13 +267,17 @@ func TestDS1_SetTiles(t *testing.T) { t.Fatal("unexpected tile was set") } - if ds1.tiles[0][1].Walls[0] != exampleTile2.Walls[0] { + if ds1.tiles[0][0].Walls[0] != exampleTile2.Walls[0] { t.Fatal("unexpected tile was set") } - if len(ds1.tiles[0][1].Walls) != len(exampleTile2.Walls) { + if len(ds1.tiles[0][0].Walls) != len(exampleTile2.Walls) { t.Fatal("unexpected tile was set") } + + if err := testIfRestorable(ds1, func(_ *DS1) {}); err != nil { + t.Errorf("unable to restore: %v", err) + } } func TestDS1_Tile(t *testing.T) { @@ -268,28 +295,36 @@ func TestDS1_SetTile(t *testing.T) { exampleTile := Tile{ Floors: []floorShadow{ - {5, 8, 9, 4, 3, 4, 2, true, 1024}, - {8, 22, 7, 9, 6, 3, 0, false, 1024}, + {5, 8, 9, 4, 3, 0, 0, true, 1024}, }, Walls: []Wall{ - {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, + {2, 0, 4, 5, 3, 2, 3, 0, 33, 99}, }, Shadows: []floorShadow{ {2, 44, 99, 2, 4, 3, 2, true, 933}, }, + Substitutions: []Substitution{ + {10244}, + }, } ds1.SetTile(0, 0, &exampleTile) - if ds1.tiles[0][0].Floors[0] != exampleTile.Floors[0] { - t.Fatal("unexpected tile was set") + test := func(ds1 *DS1) { + if ds1.tiles[0][0].Floors[0].Prop1 != exampleTile.Floors[0].Prop1 { + t.Fatal("c1.unexpected tile was set") + } + + if ds1.tiles[0][0].Walls[0].Zero != exampleTile.Walls[0].Zero { + t.Fatal("c1.unexpected tile was set") + } + + if len(ds1.tiles[0][0].Walls) != len(exampleTile.Walls) { + t.Fatal("c2.unexpected tile was set") + } } - if len(ds1.tiles[0][0].Walls) != len(exampleTile.Walls) { - t.Fatal("unexpected tile was set") - } - - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -311,11 +346,13 @@ func TestDS1_SetVersion(t *testing.T) { ds1.SetVersion(newVersion) - if newVersion != int(ds1.version) { - t.Fatal("ds1.SetVersion set version incorrectly") + test := func(ds1 *DS1) { + if newVersion != int(ds1.version) { + t.Fatal("ds1.SetVersion set version incorrectly") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -335,11 +372,13 @@ func TestDS1_SetWidth(t *testing.T) { ds1.SetWidth(int(newWidth)) - if newWidth != ds1.width { - t.Fatal("unexpected width after set") + test := func(ds1 *DS1) { + if newWidth != ds1.width { + t.Fatal("unexpected width after set") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -359,12 +398,13 @@ func TestDS1_SetHeight(t *testing.T) { ds1.SetHeight(int(newHeight)) - if newHeight != ds1.height { - fmt.Println(newHeight, ds1.height) - t.Fatal("unexpected heigth after set") + test := func(ds1 *DS1) { + if newHeight != ds1.height { + t.Fatal("unexpected heigth after set") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -386,15 +426,17 @@ func TestDS1_SetAct(t *testing.T) { t.Error("act cannot be less than 0") } - nice := 69420 + nice := 5 ds1.SetAct(nice) - if int(ds1.act) != nice { - t.Error("unexpected value for act") + test := func(ds1 *DS1) { + if int(ds1.act) != nice { + t.Error("unexpected value for act") + } } - if err := testIfRestorable(ds1); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } diff --git a/d2common/d2fileformats/d2ds1/floor_shadow.go b/d2common/d2fileformats/d2ds1/floor_shadow.go index b8856a5f..36130d39 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow.go @@ -54,7 +54,7 @@ func (f *Floor) Hidden() bool { } // Decode decodes floor-shadow record -func (f *Floor) Decode(dw uint32) { +func (f *floorShadow) Decode(dw uint32) { f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) @@ -64,7 +64,7 @@ func (f *Floor) Decode(dw uint32) { } // Encode adds Floor's bits to stream writter given -func (f *Floor) Encode(sw *d2datautils.StreamWriter) { +func (f *floorShadow) Encode(sw *d2datautils.StreamWriter) { sw.PushBits32(uint32(f.Prop1), prop1Length) sw.PushBits32(uint32(f.Sequence), sequenceLength) sw.PushBits32(uint32(f.Unknown1), unknown1Length) From 5e62b12bc45b794383a6b0bf4b85fdc486599302 Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Mon, 22 Feb 2021 21:18:35 +0100 Subject: [PATCH 8/9] 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 --- d2common/d2fileformats/d2ds1/ds1.go | 197 ++++++++++++++------- d2common/d2fileformats/d2ds1/ds1_test.go | 210 ++++++++++++++++++----- 2 files changed, 300 insertions(+), 107 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index ff1a907c..0d8879f7 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -56,11 +56,10 @@ type DS1 struct { numberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used // substitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths - dirty bool // when modifying tiles, need to perform upkeep on ds1 state - unknown1 []byte - layerStreamTypes []d2enum.LayerStreamType - unknown2 uint32 - npcIndexes []int + dirty bool // when modifying tiles, need to perform upkeep on ds1 state + unknown1 []byte + unknown2 uint32 + npcIndexes []int } // Files returns a list of file path strings. @@ -135,7 +134,6 @@ func (ds1 *DS1) SetTiles(tiles [][]Tile) { } ds1.tiles = tiles - ds1.layerStreamTypes = ds1.setupStreamLayerTypes() ds1.dirty = true ds1.update() } @@ -165,6 +163,7 @@ func (ds1 *DS1) SetTile(x, y int, t *Tile) { ds1.tiles[y][x] = *t ds1.dirty = true + ds1.update() } // Version returns the ds1's version @@ -283,8 +282,8 @@ func (ds1 *DS1) Size() (w, h int) { return int(ds1.width), int(ds1.height) } -// setSize force sets the ds1's size (width,height) -func (ds1 *DS1) setSize(w, h int) { +// SetSize force sets the ds1's size (width,height) +func (ds1 *DS1) SetSize(w, h int) { ds1.SetWidth(w) ds1.SetHeight(h) ds1.width, ds1.height = int32(w), int32(h) @@ -323,6 +322,38 @@ func (ds1 *DS1) NumberOfWallLayers() int { return int(ds1.numberOfWallLayers) } +// SetNumberOfWallLayers sets new number of tiles' walls +func (ds1 *DS1) SetNumberOfWallLayers(n int32) { + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + for v := int32(0); v < n-int32(len(ds1.tiles[y][x].Walls)); v++ { + ds1.tiles[y][x].Walls = append(ds1.tiles[y][x].Walls, Wall{}) + } + } + } + + // if n = number of walls, do nothing + if n == ds1.numberOfWallLayers { + return + } + + ds1.dirty = true + defer ds1.update() + + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + newWalls := make([]Wall, n) + for v := int32(0); v < n; v++ { + newWalls[v] = ds1.tiles[y][x].Walls[v] + } + + ds1.tiles[y][x].Walls = newWalls + } + } + + ds1.numberOfWallLayers = n +} + // NumberOfFloorLayers returns the number of floor layers per tile func (ds1 *DS1) NumberOfFloorLayers() int { if ds1.dirty { @@ -332,6 +363,41 @@ func (ds1 *DS1) NumberOfFloorLayers() int { return int(ds1.numberOfFloorLayers) } +// SetNumberOfFloorLayers sets new number of tiles' floors +func (ds1 *DS1) SetNumberOfFloorLayers(n int32) { + // calculate, how much walls is missing + missingFloors := n - ds1.numberOfFloorLayers + + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + for v := int32(0); v < missingFloors; v++ { + ds1.tiles[y][x].Floors = append(ds1.tiles[y][x].Floors, Floor{}) + } + } + } + + // if n = number of walls, do nothing + if n == ds1.numberOfFloorLayers { + return + } + + ds1.dirty = true + defer ds1.update() + + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + newFloors := make([]Floor, n) + for v := int32(0); v < n; v++ { + newFloors[v] = ds1.tiles[y][x].Floors[v] + } + + ds1.tiles[y][x].Floors = newFloors + } + } + + ds1.numberOfFloorLayers = n +} + // NumberOfShadowLayers returns the number of shadow layers per tile func (ds1 *DS1) NumberOfShadowLayers() int { if ds1.dirty { @@ -365,7 +431,18 @@ func (ds1 *DS1) update() { ds1.enforceAllTileLayersMatch() ds1.updateLayerCounts() - ds1.setSize(len(ds1.tiles[0]), len(ds1.tiles)) + ds1.SetSize(len(ds1.tiles[0]), len(ds1.tiles)) + + maxWalls := ds1.numberOfWallLayers + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + if len(ds1.tiles[y][x].Walls) > int(maxWalls) { + maxWalls = int32(len(ds1.tiles[y][x].Walls)) + } + } + } + + ds1.SetNumberOfWallLayers(maxWalls) ds1.dirty = false } @@ -405,35 +482,33 @@ func LoadDS1(fileData []byte) (*DS1, error) { err = ds1.loadHeader(br) if err != nil { - return nil, err + return nil, fmt.Errorf("loading header: %v", err) } if ds1.version >= v9 && ds1.version <= v13 { // Skipping two dwords because they are "meaningless"? ds1.unknown1, err = br.ReadBytes(unknown1BytesCount) if err != nil { - return nil, err + return nil, fmt.Errorf("reading unknown1: %v", err) } } if ds1.version >= v4 { ds1.numberOfWallLayers, err = br.ReadInt32() if err != nil { - return nil, err + return nil, fmt.Errorf("reading wall number: %v", err) } if ds1.version >= v16 { ds1.numberOfFloorLayers, err = br.ReadInt32() if err != nil { - return nil, err + return nil, fmt.Errorf("reading number of floors: %v", err) } } else { ds1.numberOfFloorLayers = 1 } } - ds1.layerStreamTypes = ds1.setupStreamLayerTypes() - ds1.tiles = make([][]Tile, ds1.height) for y := range ds1.tiles { @@ -448,22 +523,22 @@ func LoadDS1(fileData []byte) (*DS1, error) { err = ds1.loadLayerStreams(br) if err != nil { - return nil, err + return nil, fmt.Errorf("loading layer streams: %v", err) } - err = ds1.loadobjects(br) + err = ds1.loadObjects(br) if err != nil { - return nil, err + return nil, fmt.Errorf("loading objects: %v", err) } err = ds1.loadSubstitutions(br) if err != nil { - return nil, err + return nil, fmt.Errorf("loading substitutions: %v", err) } err = ds1.loadNPCs(br) if err != nil { - return nil, err + return nil, fmt.Errorf("loading npc's: %v", err) } return ds1, nil @@ -474,17 +549,17 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { ds1.version, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading version: %v", err) } ds1.width, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading width: %v", err) } ds1.height, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading height: %v", err) } ds1.width++ @@ -493,7 +568,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { if ds1.version >= v8 { ds1.act, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading act: %v", err) } ds1.act = d2math.MinInt32(d2enum.ActsNumber, ds1.act+1) @@ -502,7 +577,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { if ds1.version >= v10 { ds1.substitutionType, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution type: %v", err) } if ds1.substitutionType == 1 || ds1.substitutionType == 2 { @@ -512,7 +587,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { err = ds1.loadFileList(br) if err != nil { - return err + return fmt.Errorf("loading file list: %v", err) } return nil @@ -523,7 +598,7 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { // These files reference things that don't exist anymore :-? numberOfFiles, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading number of files: %v", err) } ds1.files = make([]string, numberOfFiles) @@ -534,7 +609,7 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { for { ch, err := br.ReadByte() if err != nil { - return err + return fmt.Errorf("reading file character: %v", err) } if ch == 0 { @@ -549,13 +624,13 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { return nil } -func (ds1 *DS1) loadobjects(br *d2datautils.StreamReader) error { +func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { if ds1.version < v2 { ds1.objects = make([]Object, 0) } else { numberOfobjects, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading number of objects: %v", err) } ds1.objects = make([]Object, numberOfobjects) @@ -564,27 +639,27 @@ func (ds1 *DS1) loadobjects(br *d2datautils.StreamReader) error { obj := Object{} objType, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading object's %d type: %v", objIdx, err) } objID, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading object's %d ID: %v", objIdx, err) } objX, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading object's %d X: %v", objIdx, err) } objY, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading object's %d Y: %v", objY, err) } objFlags, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading object's %d flags: %v", objIdx, err) } obj.Type = int(objType) @@ -613,13 +688,13 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { if ds1.version >= v18 { ds1.unknown2, err = br.ReadUInt32() if err != nil { - return err + return fmt.Errorf("reading unknown 2: %v", err) } } numberOfSubGroups, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading number of sub groups: %v", err) } ds1.substitutionGroups = make([]SubstitutionGroup, numberOfSubGroups) @@ -629,27 +704,27 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { newSub.TileX, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution's %d X: %v", subIdx, err) } newSub.TileY, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution's %d Y: %v", subIdx, err) } newSub.WidthInTiles, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution's %d W: %v", subIdx, err) } newSub.HeightInTiles, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution's %d H: %v", subIdx, err) } newSub.Unknown, err = br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading substitution's %d unknown: %v", subIdx, err) } ds1.substitutionGroups[subIdx] = newSub @@ -700,28 +775,28 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { var err error if ds1.version < v14 { - return err + return nil } numberOfNpcs, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading number of npcs: %v", err) } for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { numPaths, err := br.ReadInt32() // nolint:govet // I want to re-use this error variable if err != nil { - return err + return fmt.Errorf("reading number of paths for npc %d: %v", npcIdx, err) } npcX, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading X pos for NPC %d: %v", npcIdx, err) } npcY, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading Y pos for NPC %d: %v", npcIdx, err) } objIdx := -1 @@ -738,7 +813,7 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { if objIdx > -1 { err = ds1.loadNpcPaths(br, objIdx, int(numPaths)) if err != nil { - return err + return fmt.Errorf("loading paths for NPC %d: %v", npcIdx, err) } } else { if ds1.version >= v15 { @@ -753,8 +828,6 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { } func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) error { - var err error - if ds1.objects[objIdx].Paths == nil { ds1.objects[objIdx].Paths = make([]d2path.Path, numPaths) } @@ -764,12 +837,12 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) px, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... if err != nil { - return err + return fmt.Errorf("reading X point for path %d: %v", pathIdx, err) } py, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... if err != nil { - return err + return fmt.Errorf("reading Y point for path %d: %v", pathIdx, err) } newPath.Position = d2vector.NewPosition(float64(px), float64(py)) @@ -777,7 +850,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) if ds1.version >= v15 { action, err := br.ReadInt32() if err != nil { - return err + return fmt.Errorf("reading action for path %d: %v", pathIdx, err) } newPath.Action = int(action) @@ -786,26 +859,24 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) ds1.objects[objIdx].Paths[pathIdx] = newPath } - return err + return nil } func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { - var err error - 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, } - for lIdx := range ds1.layerStreamTypes { - layerStreamType := ds1.layerStreamTypes[lIdx] + layerStreamTypes := ds1.setupStreamLayerTypes() + for _, layerStreamType := range layerStreamTypes { for y := 0; y < int(ds1.height); y++ { for x := 0; x < int(ds1.width); x++ { - dw, err := br.ReadUInt32() //nolint:govet // i want to re-use the err variable... + dw, err := br.ReadUInt32() if err != nil { - return err + return fmt.Errorf("reading layer's dword: %v", err) } switch layerStreamType { @@ -837,7 +908,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } } - return err + return nil } // Marshal encodes ds1 back to byte slice @@ -919,9 +990,9 @@ func (ds1 *DS1) Marshal() []byte { } func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { - for lIdx := range ds1.layerStreamTypes { - layerStreamType := ds1.layerStreamTypes[lIdx] + layerStreamTypes := ds1.setupStreamLayerTypes() + for _, layerStreamType := range layerStreamTypes { for y := 0; y < int(ds1.height); y++ { for x := 0; x < int(ds1.width); x++ { dw := uint32(0) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 2d36c89b..23035a50 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" ) func exampleDS1() *DS1 { @@ -11,18 +12,38 @@ func exampleDS1() *DS1 { files: []string{"a.dt1", "b.dt1"}, objects: []Object{ {0, 0, 0, 0, 0, nil}, - {0, 1, 0, 0, 0, nil}, + {0, 1, 0, 0, 0, []d2path.Path{{}}}, {0, 2, 0, 0, 0, nil}, {0, 3, 0, 0, 0, nil}, }, tiles: [][]Tile{ // 2x2 { - Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, - Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + Tile{ + []Floor{{}}, + []Wall{{}}, + []Shadow{{}}, + []Substitution{{}}, + }, + Tile{ + []Floor{{}}, + []Wall{{}}, + []Shadow{{}}, + []Substitution{{}}, + }, }, { - Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, - Tile{[]Floor{{}}, []Wall{{}}, []Shadow{{}}, []Substitution{}}, + Tile{ + []Floor{{}}, + []Wall{{}}, + []Shadow{{}}, + []Substitution{{}}, + }, + Tile{ + []Floor{{}}, + []Wall{{}}, + []Shadow{{}}, + []Substitution{{}}, + }, }, }, substitutionGroups: nil, @@ -35,13 +56,7 @@ func exampleDS1() *DS1 { numberOfFloorLayers: 1, numberOfShadowLayers: 1, numberOfSubstitutionLayers: 1, - layerStreamTypes: []d2enum.LayerStreamType{ - d2enum.LayerStreamWall1, - d2enum.LayerStreamOrientation1, - d2enum.LayerStreamFloor1, - d2enum.LayerStreamShadow, - }, - npcIndexes: []int{}, + npcIndexes: []int{}, } } @@ -52,12 +67,15 @@ func testIfRestorable(ds1 *DS1, test func(ds1 *DS1)) error { var err error data := ds1.Marshal() + newDS1, err := LoadDS1(data) - _ = newDS1 + if err != nil { + return err + } test(newDS1) - return err + return nil } func TestDS1_Marshal(t *testing.T) { @@ -74,6 +92,10 @@ func TestDS1_Marshal(t *testing.T) { if b.width != a.width { t.Error("new ds1 does not match original") } + + if len(b.tiles) != len(a.tiles) { + t.Error("new ds1 does not batch original") + } } func TestDS1_Files(t *testing.T) { @@ -227,22 +249,21 @@ func TestDS1_SetTiles(t *testing.T) { exampleTile1 := Tile{ Floors: []floorShadow{ - {0, 0, 2, 3, 4, 55, 33, true, 999}, + {8, 7, 2, 3, 4, 55, 33, true, 999}, }, Walls: []Wall{ {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, + {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, }, Shadows: []floorShadow{ {2, 4, 5, 33, 6, 7, 0, false, 1024}, }, - Substitutions: []Substitution{ - {1024}, - }, } exampleTile2 := Tile{ Floors: []floorShadow{ - {0, 0, 2, 3, 4, 55, 33, true, 999}, + {9, 9, 2, 3, 4, 55, 33, true, 999}, + {9, 8, 2, 3, 102, 55, 33, true, 999}, }, Walls: []Wall{ {2, 3, 4, 5, 3, 2, 3, 0, 33, 99}, @@ -250,32 +271,31 @@ func TestDS1_SetTiles(t *testing.T) { Shadows: []floorShadow{ {2, 4, 5, 33, 6, 7, 0, false, 1024}, }, - Substitutions: []Substitution{ - {1234}, - }, } - tiles := [][]Tile{{exampleTile1, exampleTile2}} + tiles := [][]Tile{{exampleTile1, exampleTile2}, {exampleTile2, exampleTile1}} ds1.SetTiles(tiles) - if ds1.tiles[0][0].Floors[0] != exampleTile1.Floors[0] { - t.Fatal("unexpected tile was set") + test := func(ds1 *DS1) { + if ds1.tiles[0][0].Floors[0].Prop1 != exampleTile1.Floors[0].Prop1 { + t.Fatal("1,unexpected tile was set") + } + + if len(ds1.tiles[0][0].Walls) != len(exampleTile1.Walls) { + t.Fatal("2,unexpected tile was set") + } + + if ds1.tiles[0][1].Walls[0].Style != exampleTile2.Walls[0].Style { + t.Fatal("3,unexpected tile was set") + } + + if len(ds1.tiles[0][0].Walls) != len(exampleTile1.Walls) { + t.Fatal("4,unexpected tile was set") + } } - if len(ds1.tiles[0][0].Walls) != len(exampleTile1.Walls) { - t.Fatal("unexpected tile was set") - } - - if ds1.tiles[0][0].Walls[0] != exampleTile2.Walls[0] { - t.Fatal("unexpected tile was set") - } - - if len(ds1.tiles[0][0].Walls) != len(exampleTile2.Walls) { - t.Fatal("unexpected tile was set") - } - - if err := testIfRestorable(ds1, func(_ *DS1) {}); err != nil { + if err := testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -299,28 +319,26 @@ func TestDS1_SetTile(t *testing.T) { }, Walls: []Wall{ {2, 0, 4, 5, 3, 2, 3, 0, 33, 99}, + {5, 8, 9, 4, 3, 0, 0, 124, 221, 12}, }, Shadows: []floorShadow{ {2, 44, 99, 2, 4, 3, 2, true, 933}, }, - Substitutions: []Substitution{ - {10244}, - }, } ds1.SetTile(0, 0, &exampleTile) test := func(ds1 *DS1) { if ds1.tiles[0][0].Floors[0].Prop1 != exampleTile.Floors[0].Prop1 { - t.Fatal("c1.unexpected tile was set") + t.Fatal("unexpected tile was set") } if ds1.tiles[0][0].Walls[0].Zero != exampleTile.Walls[0].Zero { - t.Fatal("c1.unexpected tile was set") + t.Fatal("unexpected tile was set") } if len(ds1.tiles[0][0].Walls) != len(exampleTile.Walls) { - t.Fatal("c2.unexpected tile was set") + t.Fatal("unexpected tile was set") } } @@ -471,6 +489,46 @@ func TestDS1_NumberOfWalls(t *testing.T) { } } +func TestDS1_SetNumberOfWalls(t *testing.T) { + ds1 := exampleDS1() + + newNumber := int32(2) + + ds1.SetNumberOfWallLayers(newNumber) + + test := func(ds1 *DS1) { + if len(ds1.tiles[0][0].Walls) != int(newNumber) { + t.Fatal("unexpected walls length set") + } + + if ds1.NumberOfWallLayers() != int(newNumber) { + t.Fatal("unexpected walls length set") + } + } + + if err := testIfRestorable(ds1, test); err != nil { + t.Errorf("unable to restore: %v", err) + } + + newNumber = 1 + + ds1.SetNumberOfWallLayers(newNumber) + + test = func(ds1 *DS1) { + if len(ds1.tiles[0][0].Walls) != int(newNumber) { + t.Fatal("unexpected walls length set") + } + + if ds1.NumberOfWallLayers() != int(newNumber) { + t.Fatal("unexpected walls length set") + } + } + + if err := testIfRestorable(ds1, test); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + func TestDS1_NumberOfFloors(t *testing.T) { ds1 := exampleDS1() @@ -479,6 +537,46 @@ func TestDS1_NumberOfFloors(t *testing.T) { } } +func TestDS1_SetNumberOfFloors(t *testing.T) { + ds1 := exampleDS1() + + newNumber := int32(2) + + ds1.SetNumberOfFloorLayers(newNumber) + + test := func(ds1 *DS1) { + if len(ds1.tiles[0][0].Floors) != int(newNumber) { + t.Fatal("unexpected floors length set") + } + + if ds1.numberOfFloorLayers != newNumber { + t.Fatal("unexpected floors length set") + } + } + + if err := testIfRestorable(ds1, test); err != nil { + t.Errorf("unable to restore: %v", err) + } + + newNumber = 1 + + ds1.SetNumberOfFloorLayers(newNumber) + + test = func(ds1 *DS1) { + if len(ds1.tiles[0][0].Floors) != int(newNumber) { + t.Fatal("unexpected floors length set") + } + + if ds1.numberOfFloorLayers != newNumber { + t.Fatal("unexpected floors length set") + } + } + + if err := testIfRestorable(ds1, test); err != nil { + t.Errorf("unable to restore: %v", err) + } +} + func TestDS1_NumberOfShadowLayers(t *testing.T) { ds1 := exampleDS1() @@ -526,3 +624,27 @@ func TestDS1_SetSubstitutionGroups(t *testing.T) { t.Fatal("unexpected substitution group added") } } + +func TestDS1_setupStreamLayerTypes(t *testing.T) { + ds1 := exampleDS1() + + lst := []d2enum.LayerStreamType{ + d2enum.LayerStreamWall1, + d2enum.LayerStreamOrientation1, + d2enum.LayerStreamFloor1, + d2enum.LayerStreamShadow, + d2enum.LayerStreamSubstitute, + } + + layerStreamTypes := ds1.setupStreamLayerTypes() + + if len(lst) != len(layerStreamTypes) { + t.Fatal("unexpected length") + } + + for i := range lst { + if lst[i] != layerStreamTypes[i] { + t.Fatal("Unexpected type was set") + } + } +} From 4e7ec3e8430212076470745f9739578ccf788d5f Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Tue, 23 Feb 2021 21:35:06 +0100 Subject: [PATCH 9/9] 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 --- d2common/d2fileformats/d2ds1/ds1.go | 124 +++++++++++++++-------- d2common/d2fileformats/d2ds1/ds1_test.go | 39 ++++--- 2 files changed, 109 insertions(+), 54 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 0d8879f7..73c8c2c1 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -2,6 +2,7 @@ package d2ds1 import ( "fmt" + "log" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -56,10 +57,9 @@ type DS1 struct { numberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used // substitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths - dirty bool // when modifying tiles, need to perform upkeep on ds1 state - unknown1 []byte - unknown2 uint32 - npcIndexes []int + dirty bool // when modifying tiles, need to perform upkeep on ds1 state + unknown1 []byte + unknown2 uint32 } // Files returns a list of file path strings. @@ -323,35 +323,42 @@ func (ds1 *DS1) NumberOfWallLayers() int { } // SetNumberOfWallLayers sets new number of tiles' walls -func (ds1 *DS1) SetNumberOfWallLayers(n int32) { - for y := range ds1.tiles { - for x := range ds1.tiles[y] { - for v := int32(0); v < n-int32(len(ds1.tiles[y][x].Walls)); v++ { - ds1.tiles[y][x].Walls = append(ds1.tiles[y][x].Walls, Wall{}) - } - } +func (ds1 *DS1) SetNumberOfWallLayers(n int32) error { + if n > d2enum.MaxNumberOfWalls { + return fmt.Errorf("cannot set number of walls to %d: number of walls is greater than %d", n, d2enum.MaxNumberOfWalls) } - // if n = number of walls, do nothing - if n == ds1.numberOfWallLayers { - return - } - - ds1.dirty = true - defer ds1.update() - for y := range ds1.tiles { for x := range ds1.tiles[y] { - newWalls := make([]Wall, n) - for v := int32(0); v < n; v++ { - newWalls[v] = ds1.tiles[y][x].Walls[v] + // ugh, I don't know, WHY do I nned to use + // helper variable, but other way + // simply doesn't work + newWalls := ds1.tiles[y][x].Walls + for v := int32(0); v < (n - int32(len(ds1.tiles[y][x].Walls))); v++ { + newWalls = append(newWalls, Wall{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) } ds1.tiles[y][x].Walls = newWalls } } + // if n = number of walls, do nothing + if n == ds1.numberOfWallLayers { + return nil + } + + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + ds1.tiles[y][x].Walls = ds1.tiles[y][x].Walls[:n] + } + } + ds1.numberOfWallLayers = n + + ds1.dirty = true + ds1.update() + + return nil } // NumberOfFloorLayers returns the number of floor layers per tile @@ -364,21 +371,25 @@ func (ds1 *DS1) NumberOfFloorLayers() int { } // SetNumberOfFloorLayers sets new number of tiles' floors -func (ds1 *DS1) SetNumberOfFloorLayers(n int32) { - // calculate, how much walls is missing - missingFloors := n - ds1.numberOfFloorLayers +func (ds1 *DS1) SetNumberOfFloorLayers(n int32) error { + if n > d2enum.MaxNumberOfFloors { + return fmt.Errorf("cannot set number of floors to %d: number is greater than %d", n, d2enum.MaxNumberOfFloors) + } for y := range ds1.tiles { for x := range ds1.tiles[y] { - for v := int32(0); v < missingFloors; v++ { - ds1.tiles[y][x].Floors = append(ds1.tiles[y][x].Floors, Floor{}) + newFloors := ds1.tiles[y][x].Floors + for v := int32(0); v < (n - int32(len(ds1.tiles[y][x].Floors))); v++ { + newFloors = append(newFloors, Floor{}) } + + ds1.tiles[y][x].Floors = newFloors } } // if n = number of walls, do nothing if n == ds1.numberOfFloorLayers { - return + return nil } ds1.dirty = true @@ -396,6 +407,8 @@ func (ds1 *DS1) SetNumberOfFloorLayers(n int32) { } ds1.numberOfFloorLayers = n + + return nil } // NumberOfShadowLayers returns the number of shadow layers per tile @@ -434,6 +447,7 @@ func (ds1 *DS1) update() { ds1.SetSize(len(ds1.tiles[0]), len(ds1.tiles)) maxWalls := ds1.numberOfWallLayers + for y := range ds1.tiles { for x := range ds1.tiles[y] { if len(ds1.tiles[y][x].Walls) > int(maxWalls) { @@ -442,7 +456,25 @@ func (ds1 *DS1) update() { } } - ds1.SetNumberOfWallLayers(maxWalls) + err := ds1.SetNumberOfWallLayers(maxWalls) + if err != nil { + log.Print(err) + } + + maxFloors := ds1.numberOfFloorLayers + + for y := range ds1.tiles { + for x := range ds1.tiles[y] { + if len(ds1.tiles[y][x].Floors) > int(maxFloors) { + maxFloors = int32(len(ds1.tiles[y][x].Floors)) + } + } + } + + err = ds1.SetNumberOfFloorLayers(maxFloors) + if err != nil { + log.Print(err) + } ds1.dirty = false } @@ -733,7 +765,8 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { return err } -func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { +// GetStreamLayerTypes returns layers used in ds1 +func (ds1 *DS1) GetStreamLayerTypes() []d2enum.LayerStreamType { var layerStream []d2enum.LayerStreamType if ds1.version < v4 { @@ -804,7 +837,6 @@ 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 } @@ -869,7 +901,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { 0x0F, 0x10, 0x11, 0x12, 0x14, } - layerStreamTypes := ds1.setupStreamLayerTypes() + layerStreamTypes := ds1.GetStreamLayerTypes() for _, layerStreamType := range layerStreamTypes { for y := 0; y < int(ds1.height); y++ { @@ -990,7 +1022,7 @@ func (ds1 *DS1) Marshal() []byte { } func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { - layerStreamTypes := ds1.setupStreamLayerTypes() + layerStreamTypes := ds1.GetStreamLayerTypes() for _, layerStreamType := range layerStreamTypes { for y := 0; y < int(ds1.height); y++ { @@ -1022,21 +1054,29 @@ func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { } 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(ds1.npcIndexes))) + sw.PushUint32(uint32(len(objectsWithPaths))) // 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 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 _, j := range ds1.objects[i].Paths { - sw.PushUint32(uint32(j.Position.X())) - sw.PushUint32(uint32(j.Position.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(j.Action)) + sw.PushUint32(uint32(path.Action)) } } } diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 23035a50..c56e2f32 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -56,7 +56,6 @@ func exampleDS1() *DS1 { numberOfFloorLayers: 1, numberOfShadowLayers: 1, numberOfSubstitutionLayers: 1, - npcIndexes: []int{}, } } @@ -490,11 +489,16 @@ func TestDS1_NumberOfWalls(t *testing.T) { } func TestDS1_SetNumberOfWalls(t *testing.T) { + var err error + ds1 := exampleDS1() - newNumber := int32(2) + newNumber := int32(4) - ds1.SetNumberOfWallLayers(newNumber) + err = ds1.SetNumberOfWallLayers(newNumber) + if err != nil { + t.Errorf("error setting number of walls: %v", err) + } test := func(ds1 *DS1) { if len(ds1.tiles[0][0].Walls) != int(newNumber) { @@ -506,13 +510,16 @@ func TestDS1_SetNumberOfWalls(t *testing.T) { } } - if err := testIfRestorable(ds1, test); err != nil { + if err = testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } newNumber = 1 - ds1.SetNumberOfWallLayers(newNumber) + err = ds1.SetNumberOfWallLayers(newNumber) + if err != nil { + t.Errorf("error setting number of walls: %v", err) + } test = func(ds1 *DS1) { if len(ds1.tiles[0][0].Walls) != int(newNumber) { @@ -524,7 +531,7 @@ func TestDS1_SetNumberOfWalls(t *testing.T) { } } - if err := testIfRestorable(ds1, test); err != nil { + if err = testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -538,11 +545,16 @@ func TestDS1_NumberOfFloors(t *testing.T) { } func TestDS1_SetNumberOfFloors(t *testing.T) { + var err error + ds1 := exampleDS1() newNumber := int32(2) - ds1.SetNumberOfFloorLayers(newNumber) + err = ds1.SetNumberOfFloorLayers(newNumber) + if err != nil { + t.Errorf("error setting number of floors: %v", err) + } test := func(ds1 *DS1) { if len(ds1.tiles[0][0].Floors) != int(newNumber) { @@ -554,13 +566,16 @@ func TestDS1_SetNumberOfFloors(t *testing.T) { } } - if err := testIfRestorable(ds1, test); err != nil { + if err = testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } newNumber = 1 - ds1.SetNumberOfFloorLayers(newNumber) + err = ds1.SetNumberOfFloorLayers(newNumber) + if err != nil { + t.Errorf("error setting number of floors: %v", err) + } test = func(ds1 *DS1) { if len(ds1.tiles[0][0].Floors) != int(newNumber) { @@ -572,7 +587,7 @@ func TestDS1_SetNumberOfFloors(t *testing.T) { } } - if err := testIfRestorable(ds1, test); err != nil { + if err = testIfRestorable(ds1, test); err != nil { t.Errorf("unable to restore: %v", err) } } @@ -625,7 +640,7 @@ func TestDS1_SetSubstitutionGroups(t *testing.T) { } } -func TestDS1_setupStreamLayerTypes(t *testing.T) { +func TestDS1_GetStreamLayerTypes(t *testing.T) { ds1 := exampleDS1() lst := []d2enum.LayerStreamType{ @@ -636,7 +651,7 @@ func TestDS1_setupStreamLayerTypes(t *testing.T) { d2enum.LayerStreamSubstitute, } - layerStreamTypes := ds1.setupStreamLayerTypes() + layerStreamTypes := ds1.GetStreamLayerTypes() if len(lst) != len(layerStreamTypes) { t.Fatal("unexpected length")