From ec47f16cc457539e961b2a327fcf12b4ff2284e4 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 02:18:37 -0800 Subject: [PATCH 01/33] 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 02/33] 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 03/33] 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 04/33] 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 05/33] 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 06/33] 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 07/33] 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 08/33] 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 09/33] 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") From 5e0e51d5e270ff3f48ff5a5b5f0d73a334f80b13 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 02:18:37 -0800 Subject: [PATCH 10/33] 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 | 4 +- d2core/d2map/d2mapengine/engine.go | 4 +- d2core/d2records/level_presets_loader.go | 2 +- utils/extract-mpq/doc.go | 2 +- 11 files changed, 781 insertions(+), 154 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 d6c5c52d..60e7d9c7 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/" 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 194c1e467c7925669bcfa6129af82e39386b2093 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 11/33] 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 ee758b785c17ab0df6fb5984557dc3106112125e Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:15:46 -0800 Subject: [PATCH 12/33] 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 87d0803a4f1c06626d0cd02032f0047a2cee4127 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:16:10 -0800 Subject: [PATCH 13/33] 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 2353ef2e70948341296e27de9ce06c4c03f64f74 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:23:49 -0800 Subject: [PATCH 14/33] 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 4bc4fa0221d68c7ef63628534235f409e6a83996 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 17 Feb 2021 10:57:38 -0800 Subject: [PATCH 15/33] 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 ca47018e8a4d0ba52e449d08053bf40f1ec0c4b0 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 16/33] 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 5706a1c19a745ca10c4fa8089097a31238dbfd00 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 17/33] 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 a33117fb56eeebf77aab0f1f46e1e99123056420 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 18/33] 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") From 5dfca5e9f35e0be6b97ab05bc5f81a966f9f0b06 Mon Sep 17 00:00:00 2001 From: gravestench Date: Mon, 1 Mar 2021 02:03:03 -0800 Subject: [PATCH 19/33] WIP --- d2common/d2enum/layer_stream_type.go | 20 - d2common/d2fileformats/d2ds1/doc.go | 2 +- d2common/d2fileformats/d2ds1/ds1.go | 1106 ++++++----------- d2common/d2fileformats/d2ds1/ds1_layers.go | 432 +++++++ .../d2fileformats/d2ds1/ds1_layers_test.go | 152 +++ d2common/d2fileformats/d2ds1/ds1_test.go | 664 ---------- d2common/d2fileformats/d2ds1/ds1_version.go | 59 + d2common/d2fileformats/d2ds1/floor_shadow.go | 74 -- d2common/d2fileformats/d2ds1/layer.go | 143 +++ d2common/d2fileformats/d2ds1/layer_test.go | 29 + d2common/d2fileformats/d2ds1/substitution.go | 6 - d2common/d2fileformats/d2ds1/tile.go | 127 +- d2common/d2fileformats/d2ds1/wall.go | 45 - d2core/d2asset/asset_manager.go | 2 +- d2core/d2map/d2mapengine/engine.go | 4 +- d2core/d2map/d2mapengine/map_tile.go | 5 +- d2core/d2map/d2mapgen/act1_overworld.go | 4 +- d2core/d2map/d2maprenderer/renderer.go | 6 +- d2core/d2map/d2maprenderer/tile_cache.go | 6 +- d2core/d2map/d2mapstamp/factory.go | 2 +- d2core/d2map/d2mapstamp/stamp.go | 46 +- et | 73 ++ 22 files changed, 1410 insertions(+), 1597 deletions(-) delete mode 100644 d2common/d2enum/layer_stream_type.go create mode 100644 d2common/d2fileformats/d2ds1/ds1_layers.go create mode 100644 d2common/d2fileformats/d2ds1/ds1_layers_test.go create mode 100644 d2common/d2fileformats/d2ds1/ds1_version.go delete mode 100644 d2common/d2fileformats/d2ds1/floor_shadow.go create mode 100644 d2common/d2fileformats/d2ds1/layer.go create mode 100644 d2common/d2fileformats/d2ds1/layer_test.go delete mode 100644 d2common/d2fileformats/d2ds1/substitution.go delete mode 100644 d2common/d2fileformats/d2ds1/wall.go create mode 100644 et diff --git a/d2common/d2enum/layer_stream_type.go b/d2common/d2enum/layer_stream_type.go deleted file mode 100644 index 08233ee9..00000000 --- a/d2common/d2enum/layer_stream_type.go +++ /dev/null @@ -1,20 +0,0 @@ -package d2enum - -// LayerStreamType represents a layer stream type -type LayerStreamType int - -// Layer stream types -const ( - LayerStreamWall1 LayerStreamType = iota - LayerStreamWall2 - LayerStreamWall3 - LayerStreamWall4 - LayerStreamOrientation1 - LayerStreamOrientation2 - LayerStreamOrientation3 - LayerStreamOrientation4 - LayerStreamFloor1 - LayerStreamFloor2 - LayerStreamShadow - LayerStreamSubstitute -) diff --git a/d2common/d2fileformats/d2ds1/doc.go b/d2common/d2fileformats/d2ds1/doc.go index 87277713..be3caec3 100644 --- a/d2common/d2fileformats/d2ds1/doc.go +++ b/d2common/d2fileformats/d2ds1/doc.go @@ -1,2 +1,2 @@ -// Package d2ds1 provides functionality for loading/processing DS1 files +// Package d2ds1 provides functionality for loading/processing DS1 Files package d2ds1 diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 73c8c2c1..e13e5890 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -2,7 +2,6 @@ package d2ds1 import ( "fmt" - "log" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -14,25 +13,11 @@ import ( const ( subType1 = 1 subType2 = 2 - v2 = 2 - v3 = 3 - v4 = 4 - v7 = 7 - v8 = 8 - v9 = 9 - v10 = 10 - v12 = 12 - v13 = 13 - v14 = 14 - v15 = 15 - v16 = 16 - v18 = 18 ) const ( wallZeroBitmask = 0xFFFFFF00 wallZeroOffset = 8 - wallTypeBitmask = 0x000000FF ) @@ -42,517 +27,88 @@ 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 [][]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 + *ds1Layers + Files []string // FilePtr table of file string pointers + Objects []Object // Objects + substitutionGroups []SubstitutionGroup // Substitution groups for the DS1 - dirty bool // when modifying tiles, need to perform upkeep on ds1 state - unknown1 []byte - unknown2 uint32 + version ds1version + 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 + unknown1 []byte + unknown2 uint32 } -// 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 +const ( + defaultNumFloors = 1 + defaultNumShadows = maxShadowLayers + defaultNumSubstitutions = maxSubstitutionLayers +) + +// Unmarshal the given bytes to a DS1 struct +func Unmarshal(fileData []byte) (*DS1, error) { + return (&DS1{}).Unmarshal(fileData) } -// 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) error { - for idx := range ds1.files { - if ds1.files[idx] == file { - ds1.files = append(ds1.files[:idx], ds1.files[idx+1:]...) - - return nil - } - } - - return fmt.Errorf("file %s not found", file) -} - -// 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 - ds1.update() -} - -// 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 - } - - 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 - ds1.update() -} - -// 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...) - } - } - - ds1.width = int32(w) -} - -// 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() - } - } - - ds1.tiles = append(ds1.tiles, newRows...) - } - - // 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 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 -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) -} - -// SetNumberOfWallLayers sets new number of tiles' walls -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) - } - - for y := range ds1.tiles { - for x := range ds1.tiles[y] { - // 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 -func (ds1 *DS1) NumberOfFloorLayers() int { - if ds1.dirty { - ds1.update() - } - - return int(ds1.numberOfFloorLayers) -} - -// SetNumberOfFloorLayers sets new number of tiles' floors -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] { - 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 nil - } - - 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 - - return nil -} - -// 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)) - - 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)) - } - } - } - - 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 -} - -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, - numberOfFloorLayers: 0, - numberOfWallLayers: 0, - numberOfShadowLayers: 1, - numberOfSubstitutionLayers: 0, - } +// Unmarshal the given bytes to a DS1 struct +func (ds1 *DS1) Unmarshal(fileData []byte) (*DS1, error) { + ds1.ds1Layers = &ds1Layers{} br := d2datautils.CreateStreamReader(fileData) var err error + var numWalls, numFloors, numShadows, numSubstitutions int32 + + numFloors = defaultNumFloors + numShadows = defaultNumShadows + numSubstitutions = defaultNumSubstitutions + err = ds1.loadHeader(br) if err != nil { return nil, fmt.Errorf("loading header: %v", err) } - if ds1.version >= v9 && ds1.version <= v13 { - // Skipping two dwords because they are "meaningless"? + if ds1.version.hasUnknown1Bytes() { ds1.unknown1, err = br.ReadBytes(unknown1BytesCount) if err != nil { return nil, fmt.Errorf("reading unknown1: %v", err) } } - if ds1.version >= v4 { - ds1.numberOfWallLayers, err = br.ReadInt32() + if ds1.version.specifiesWalls() { + numWalls, err = br.ReadInt32() if err != nil { return nil, fmt.Errorf("reading wall number: %v", err) } - if ds1.version >= v16 { - ds1.numberOfFloorLayers, err = br.ReadInt32() + if ds1.version.specifiesFloors() { + numFloors, err = br.ReadInt32() if err != nil { - return nil, fmt.Errorf("reading number of floors: %v", err) + return nil, fmt.Errorf("reading number of Floors: %v", err) } - } else { - ds1.numberOfFloorLayers = 1 } } - ds1.tiles = make([][]Tile, ds1.height) - - 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) - } + for ; numWalls > 0; numWalls-- { + ds1.PushWall(&layer{}) + ds1.PushOrientation(&layer{}) } + for ; numShadows > 0; numShadows-- { + ds1.PushShadow(&layer{}) + } + + for ; numFloors > 0; numFloors-- { + ds1.PushFloor(&layer{}) + } + + for ; numSubstitutions > 0; numSubstitutions-- { + ds1.PushSubstitution(&layer{}) + } + + ds1.SetSize(ds1.width, ds1.height) + err = ds1.loadLayerStreams(br) if err != nil { return nil, fmt.Errorf("loading layer streams: %v", err) @@ -560,12 +116,12 @@ func LoadDS1(fileData []byte) (*DS1, error) { err = ds1.loadObjects(br) if err != nil { - return nil, fmt.Errorf("loading objects: %v", err) + return nil, fmt.Errorf("loading Objects: %v", err) } err = ds1.loadSubstitutions(br) if err != nil { - return nil, fmt.Errorf("loading substitutions: %v", err) + return nil, fmt.Errorf("loading Substitutions: %v", err) } err = ds1.loadNPCs(br) @@ -579,41 +135,48 @@ func LoadDS1(fileData []byte) (*DS1, error) { func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { var err error - ds1.version, err = br.ReadInt32() + var width, height int32 + + v, err := br.ReadInt32() if err != nil { return fmt.Errorf("reading version: %v", err) } - ds1.width, err = br.ReadInt32() + ds1.version = ds1version(v) + + width, err = br.ReadInt32() if err != nil { return fmt.Errorf("reading width: %v", err) } - ds1.height, err = br.ReadInt32() + height, err = br.ReadInt32() if err != nil { return fmt.Errorf("reading height: %v", err) } - ds1.width++ - ds1.height++ + //width++ + //height++ - if ds1.version >= v8 { - ds1.act, err = br.ReadInt32() + ds1.SetSize(int(width), int(height)) + + if ds1.version.specifiesAct() { + ds1.Act, err = br.ReadInt32() if err != nil { - return fmt.Errorf("reading act: %v", err) + return fmt.Errorf("reading Act: %v", err) } - ds1.act = d2math.MinInt32(d2enum.ActsNumber, ds1.act+1) + ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1) } - if ds1.version >= v10 { + if ds1.version.specifiesSubstitutionType() { ds1.substitutionType, err = br.ReadInt32() if err != nil { return fmt.Errorf("reading substitution type: %v", err) } - if ds1.substitutionType == 1 || ds1.substitutionType == 2 { - ds1.numberOfSubstitutionLayers = 1 + switch ds1.substitutionType { + case subType1, subType2: + ds1.PushSubstitution(&layer{}) } } @@ -626,30 +189,32 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { } func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { - if ds1.version >= v3 { - // These files reference things that don't exist anymore :-? - numberOfFiles, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading number of files: %v", err) - } + if !ds1.version.hasFileList() { + return nil + } - ds1.files = make([]string, numberOfFiles) + // These Files reference things that don't exist anymore :-? + numberOfFiles, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading number of Files: %v", err) + } - for i := 0; i < int(numberOfFiles); i++ { - ds1.files[i] = "" + ds1.Files = make([]string, numberOfFiles) - for { - ch, err := br.ReadByte() - if err != nil { - return fmt.Errorf("reading file character: %v", err) - } + for i := 0; i < int(numberOfFiles); i++ { + ds1.Files[i] = "" - if ch == 0 { - break - } - - ds1.files[i] += string(ch) + for { + ch, err := br.ReadByte() + if err != nil { + return fmt.Errorf("reading file character: %v", err) } + + if ch == 0 { + break + } + + ds1.Files[i] += string(ch) } } @@ -657,51 +222,53 @@ func (ds1 *DS1) loadFileList(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 !ds1.version.hasObjects() { + ds1.Objects = make([]Object, 0) + return nil + } + + numObjects, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading number of Objects: %v", err) + } + + ds1.Objects = make([]Object, numObjects) + + for objIdx := 0; objIdx < int(numObjects); objIdx++ { + obj := Object{} + objType, err := br.ReadInt32() + if err != nil { - return fmt.Errorf("reading number of objects: %v", err) + return fmt.Errorf("reading object's %d type: %v", objIdx, err) } - ds1.objects = make([]Object, numberOfobjects) - - for objIdx := 0; objIdx < int(numberOfobjects); objIdx++ { - obj := Object{} - objType, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading object's %d type: %v", objIdx, err) - } - - objID, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading object's %d ID: %v", objIdx, err) - } - - objX, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading object's %d X: %v", objIdx, err) - } - - objY, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading object's %d Y: %v", objY, err) - } - - objFlags, err := br.ReadInt32() - if err != nil { - return fmt.Errorf("reading object's %d flags: %v", objIdx, err) - } - - obj.Type = int(objType) - obj.ID = int(objID) - obj.X = int(objX) - obj.Y = int(objY) - obj.Flags = int(objFlags) - - ds1.objects[objIdx] = obj + objID, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading object's %d ID: %v", objIdx, err) } + + objX, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading object's %d X: %v", objIdx, err) + } + + objY, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading object's %d Y: %v", objY, err) + } + + objFlags, err := br.ReadInt32() + if err != nil { + return fmt.Errorf("reading object's %d flags: %v", objIdx, err) + } + + obj.Type = int(objType) + obj.ID = int(objID) + obj.X = int(objX) + obj.Y = int(objY) + obj.Flags = int(objFlags) + + ds1.Objects[objIdx] = obj } return nil @@ -710,14 +277,15 @@ 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.hasSubstitutions() && + (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) if !hasSubstitutions { ds1.substitutionGroups = make([]SubstitutionGroup, 0) return nil } - if ds1.version >= v18 { + if ds1.version.hasUnknown2Bytes() { ds1.unknown2, err = br.ReadUInt32() if err != nil { return fmt.Errorf("reading unknown 2: %v", err) @@ -765,40 +333,52 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { return err } -// GetStreamLayerTypes returns layers used in ds1 -func (ds1 *DS1) GetStreamLayerTypes() []d2enum.LayerStreamType { - var layerStream []d2enum.LayerStreamType +func (ds1 *DS1) getLayerSchema() []layerStreamType { + var layerStream []layerStreamType if ds1.version < v4 { - layerStream = []d2enum.LayerStreamType{ - d2enum.LayerStreamWall1, - d2enum.LayerStreamFloor1, - d2enum.LayerStreamOrientation1, - d2enum.LayerStreamSubstitute, - d2enum.LayerStreamShadow, + layerStream = []layerStreamType{ + layerStreamWall1, + layerStreamFloor1, + layerStreamOrientation1, + layerStreamSubstitute1, + layerStreamShadow1, } - } else { - // nolint:gomnd // constant - layerStream = make([]d2enum.LayerStreamType, - (ds1.numberOfWallLayers*2)+ds1.numberOfFloorLayers+ds1.numberOfShadowLayers+ds1.numberOfSubstitutionLayers) - layerIdx := 0 - 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.numberOfFloorLayers); i++ { - layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamFloor1) + i) - layerIdx++ - } - if ds1.numberOfShadowLayers > 0 { - layerStream[layerIdx] = d2enum.LayerStreamShadow - layerIdx++ - } - if ds1.numberOfSubstitutionLayers > 0 { - layerStream[layerIdx] = d2enum.LayerStreamSubstitute - } + return layerStream + } + + numWalls := len(ds1.Walls) + numOrientations := numWalls + numFloors := len(ds1.Floors) + numShadows := len(ds1.Shadows) + numSubs := len(ds1.Substitutions) + numLayers := numWalls + numOrientations + numFloors + numShadows + numSubs + + layerStream = make([]layerStreamType, numLayers) + + layerIdx := 0 + + for i := 0; i < numWalls; i++ { + layerStream[layerIdx] = layerStreamType(int(layerStreamWall1) + i) + layerIdx++ + + layerStream[layerIdx] = layerStreamType(int(layerStreamOrientation1) + i) + layerIdx++ + } + + for i := 0; i < numFloors; i++ { + layerStream[layerIdx] = layerStreamType(int(layerStreamFloor1) + i) + layerIdx++ + } + + if numShadows > 0 { + layerStream[layerIdx] = layerStreamShadow1 + layerIdx++ + } + + if numSubs > 0 { + layerStream[layerIdx] = layerStreamSubstitute1 } return layerStream @@ -834,7 +414,7 @@ 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 @@ -860,8 +440,8 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { } func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) 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++ { @@ -888,7 +468,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 nil @@ -901,23 +481,23 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { 0x0F, 0x10, 0x11, 0x12, 0x14, } - layerStreamTypes := ds1.GetStreamLayerTypes() + layerStreamTypes := ds1.getLayerSchema() for _, layerStreamType := range layerStreamTypes { - for y := 0; y < int(ds1.height); y++ { - for x := 0; x < int(ds1.width); x++ { + for y := 0; y < ds1.height; y++ { + for x := 0; x < ds1.width; x++ { dw, err := br.ReadUInt32() if err != nil { return fmt.Errorf("reading layer's dword: %v", err) } 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) - case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, - d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: - wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) + case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: + wallIndex := int(layerStreamType) - int(layerStreamWall1) + ds1.Walls[wallIndex].Tile(x, y).DecodeWall(dw) + case layerStreamOrientation1, layerStreamOrientation2, + layerStreamOrientation3, layerStreamOrientation4: + wallIndex := int(layerStreamType) - int(layerStreamOrientation1) c := int32(dw & wallTypeBitmask) if ds1.version < v7 { @@ -926,15 +506,16 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } } - 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) - case d2enum.LayerStreamShadow: - ds1.tiles[y][x].Shadows[0].Decode(dw) - case d2enum.LayerStreamSubstitute: - ds1.tiles[y][x].Substitutions[0].Unknown = dw + tile := ds1.Orientations[wallIndex].Tile(x, y) + tile.Type = d2enum.TileType(c) + tile.Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset) + case layerStreamFloor1, layerStreamFloor2: + floorIndex := int(layerStreamType) - int(layerStreamFloor1) + ds1.Floors[floorIndex].Tile(x, y).DecodeFloor(dw) + case layerStreamShadow1: + ds1.Floors[0].Tile(x, y).DecodeShadow(dw) + case layerStreamSubstitute1: + ds1.Floors[0].Tile(x, y).Substitution = dw } } } @@ -943,141 +524,150 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { return nil } -// Marshal encodes ds1 back to byte slice -func (ds1 *DS1) Marshal() []byte { - // create stream writer - sw := d2datautils.CreateStreamWriter() - - // Step 1 - encode header - sw.PushInt32(ds1.version) - sw.PushInt32(ds1.width - 1) - sw.PushInt32(ds1.height - 1) - - if ds1.version >= v8 { - sw.PushInt32(ds1.act - 1) - } - - if ds1.version >= v10 { - sw.PushInt32(ds1.substitutionType) - } - - if ds1.version >= v3 { - sw.PushInt32(int32(len(ds1.files))) - - for _, i := range ds1.files { - sw.PushBytes([]byte(i)...) - - // separator - sw.PushBytes(0) - } - } - - if ds1.version >= v9 && ds1.version <= v13 { - sw.PushBytes(ds1.unknown1...) - } - - if ds1.version >= v4 { - sw.PushInt32(ds1.numberOfWallLayers) - - if ds1.version >= v16 { - sw.PushInt32(ds1.numberOfFloorLayers) - } - } - - // Step 2 - encode layers - ds1.encodeLayers(sw) - - // Step 3 - encode objects - if !(ds1.version < v2) { - sw.PushInt32(int32(len(ds1.objects))) - - for _, i := range ds1.objects { - sw.PushUint32(uint32(i.Type)) - sw.PushUint32(uint32(i.ID)) - sw.PushUint32(uint32(i.X)) - sw.PushUint32(uint32(i.Y)) - sw.PushUint32(uint32(i.Flags)) - } - } - - // Step 4 - encode substitutions - if ds1.version >= v12 && (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) { - sw.PushUint32(ds1.unknown2) - - sw.PushUint32(uint32(len(ds1.substitutionGroups))) - - for _, i := range ds1.substitutionGroups { - sw.PushInt32(i.TileX) - sw.PushInt32(i.TileY) - sw.PushInt32(i.WidthInTiles) - sw.PushInt32(i.HeightInTiles) - sw.PushInt32(i.Unknown) - } - } - - // Step 5 - encode NPC's and its paths - ds1.encodeNPCs(sw) - - return sw.GetBytes() +// SetSize sets the size of all layers in the DS1 +func (ds1 *DS1) SetSize(w, h int) { + ds1.ds1Layers.SetSize(w, h) } -func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { - layerStreamTypes := ds1.GetStreamLayerTypes() - - for _, layerStreamType := range layerStreamTypes { - for y := 0; y < int(ds1.height); y++ { - for x := 0; x < int(ds1.width); x++ { - dw := uint32(0) - - switch layerStreamType { - case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: - wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - ds1.tiles[y][x].Walls[wallIndex].Encode(sw) - case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, - d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: - wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) - dw |= uint32(ds1.tiles[y][x].Walls[wallIndex].Type) - dw |= (uint32(ds1.tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset - - sw.PushUint32(dw) - case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: - floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) - ds1.tiles[y][x].Floors[floorIndex].Encode(sw) - case d2enum.LayerStreamShadow: - ds1.tiles[y][x].Shadows[0].Encode(sw) - case d2enum.LayerStreamSubstitute: - sw.PushUint32(ds1.tiles[y][x].Substitutions[0].Unknown) - } - } - } - } -} - -func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { - objectsWithPaths := make([]int, 0) - - for n, obj := range ds1.objects { - if len(obj.Paths) != 0 { - objectsWithPaths = append(objectsWithPaths, n) - } - } - - // Step 5.1 - encode npc's - sw.PushUint32(uint32(len(objectsWithPaths))) - - // Step 5.2 - enoce npcs' paths - for objectIdx := range objectsWithPaths { - sw.PushUint32(uint32(len(ds1.objects[objectIdx].Paths))) - sw.PushUint32(uint32(ds1.objects[objectIdx].X)) - sw.PushUint32(uint32(ds1.objects[objectIdx].Y)) - - for _, path := range ds1.objects[objectIdx].Paths { - sw.PushUint32(uint32(path.Position.X())) - sw.PushUint32(uint32(path.Position.Y())) - - if ds1.version >= v15 { - sw.PushUint32(uint32(path.Action)) - } - } - } -} +// +// // Marshal encodes ds1 back to byte slice +// func (ds1 *DS1) Marshal() []byte { +// // create stream writer +// sw := d2datautils.CreateStreamWriter() +// +// // Step 1 - encode header +// sw.PushInt32(ds1.version) +// sw.PushInt32(ds1.width - 1) +// sw.PushInt32(ds1.height - 1) +// +// if ds1.version >= v8 { +// sw.PushInt32(ds1.Act - 1) +// } +// +// if ds1.version >= v10 { +// sw.PushInt32(ds1.substitutionType) +// } +// +// if ds1.version >= v3 { +// sw.PushInt32(int32(len(ds1.Files))) +// +// for _, i := range ds1.Files { +// sw.PushBytes([]byte(i)...) +// +// // separator +// sw.PushBytes(0) +// } +// } +// +// if ds1.version >= v9 && ds1.version <= v13 { +// sw.PushBytes(ds1.unknown1...) +// } +// +// if ds1.version >= v4 { +// sw.PushInt32(ds1.numberOfWallLayers) +// +// if ds1.version >= v16 { +// sw.PushInt32(ds1.numberOfFloorLayers) +// } +// } +// +// // Step 2 - encode grid +// ds1.encodeLayers(sw) +// +// // Step 3 - encode Objects +// if !(ds1.version < v2) { +// sw.PushInt32(int32(len(ds1.Objects))) +// +// for _, i := range ds1.Objects { +// sw.PushUint32(uint32(i.Type)) +// sw.PushUint32(uint32(i.ID)) +// sw.PushUint32(uint32(i.X)) +// sw.PushUint32(uint32(i.Y)) +// sw.PushUint32(uint32(i.Flags)) +// } +// } +// +// // Step 4 - encode Substitutions +// if ds1.version >= v12 && (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) { +// sw.PushUint32(ds1.unknown2) +// +// sw.PushUint32(uint32(len(ds1.substitutionGroups))) +// +// for _, i := range ds1.substitutionGroups { +// sw.PushInt32(i.TileX) +// sw.PushInt32(i.TileY) +// sw.PushInt32(i.WidthInTiles) +// sw.PushInt32(i.HeightInTiles) +// sw.PushInt32(i.Unknown) +// } +// } +// +// // Step 5 - encode NPC's and its paths +// ds1.encodeNPCs(sw) +// +// return sw.GetBytes() +// } +// +// func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { +// /* +// layerStreamTypes := ds1.getLayerSchema() +// +// for _, layerStreamType := range layerStreamTypes { +// for y := 0; y < int(ds1.height); y++ { +// for x := 0; x < int(ds1.width); x++ { +// dw := uint32(0) +// +// switch layerStreamType { +// case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: +// wallIndex := int(layerStreamType) - int(layerStreamWall1) +// ds1.tiles[y][x].Walls[wallIndex].Encode(sw) +// case layerStreamOrientation1, layerStreamOrientation2, +// layerStreamOrientation3, layerStreamOrientation4: +// wallIndex := int(layerStreamType) - int(layerStreamOrientation1) +// dw |= uint32(ds1.tiles[y][x].Walls[wallIndex].Type) +// dw |= (uint32(ds1.tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset +// +// sw.PushUint32(dw) +// case layerStreamFloor1, layerStreamFloor2: +// floorIndex := int(layerStreamType) - int(layerStreamFloor1) +// ds1.tiles[y][x].Floors[floorIndex].Encode(sw) +// case d2enum.layerStreamShadow1: +// ds1.tiles[y][x].Shadows[0].Encode(sw) +// case d2enum.layerStreamSubstitute1: +// sw.PushUint32(ds1.tiles[y][x].Substitutions[0].Unknown) +// } +// } +// } +// } +// */ +// } +// +// func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { +// objectsWithPaths := make([]int, 0) +// +// for n, obj := range ds1.Objects { +// if len(obj.Paths) != 0 { +// objectsWithPaths = append(objectsWithPaths, n) +// } +// } +// +// // Step 5.1 - encode npc's +// sw.PushUint32(uint32(len(objectsWithPaths))) +// +// // Step 5.2 - enoce npcs' paths +// for objectIdx := range objectsWithPaths { +// sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths))) +// sw.PushUint32(uint32(ds1.Objects[objectIdx].X)) +// sw.PushUint32(uint32(ds1.Objects[objectIdx].Y)) +// +// for _, path := range ds1.Objects[objectIdx].Paths { +// sw.PushUint32(uint32(path.Position.X())) +// sw.PushUint32(uint32(path.Position.Y())) +// +// if ds1.version >= v15 { +// sw.PushUint32(uint32(path.Action)) +// } +// } +// } +// } +// diff --git a/d2common/d2fileformats/d2ds1/ds1_layers.go b/d2common/d2fileformats/d2ds1/ds1_layers.go new file mode 100644 index 00000000..7b37d4ea --- /dev/null +++ b/d2common/d2fileformats/d2ds1/ds1_layers.go @@ -0,0 +1,432 @@ +package d2ds1 + +const ( + maxWallLayers = 4 + maxOrientationLayers = 4 + maxFloorLayers = 2 + maxShadowLayers = 1 + maxSubstitutionLayers = 1 +) + +type layerGroupType int + +const ( + floorLayerGroup layerGroupType = iota + wallLayerGroup + orientationLayerGroup + shadowLayerGroup + substitutionLayerGroup +) + +type layerGroup []*layer + +type ds1Layers struct { + width, height int + Floors layerGroup + Walls layerGroup + Orientations layerGroup + Shadows layerGroup + Substitutions layerGroup +} + +func (l *ds1Layers) ensureInit() { + if l.Floors == nil { + l.Floors = make(layerGroup, 0) + } + + if l.Walls == nil { + l.Walls = make(layerGroup, 0) + } + + if l.Orientations == nil { + l.Orientations = make(layerGroup, 0) + } + + if l.Shadows == nil { + l.Shadows = make(layerGroup, 0) + } + + if l.Substitutions == nil { + l.Substitutions = make(layerGroup, 0) + } +} + +// removes nil layers from all layer groups +func (l *ds1Layers) cull() { + l.cullNilLayers(floorLayerGroup) + l.cullNilLayers(wallLayerGroup) + l.cullNilLayers(orientationLayerGroup) + l.cullNilLayers(shadowLayerGroup) + l.cullNilLayers(substitutionLayerGroup) +} + +// removes nil layers of given layer group type +func (l *ds1Layers) cullNilLayers(t layerGroupType) { + var group *layerGroup + + switch t { + case floorLayerGroup: + group = &l.Floors + case wallLayerGroup: + group = &l.Walls + case orientationLayerGroup: + group = &l.Orientations + case shadowLayerGroup: + group = &l.Shadows + case substitutionLayerGroup: + group = &l.Substitutions + default: + return + } + + // from last to first layer, remove first encountered nil layer and restart the culling procedure. + // exit culling procedure when no nil layers are found in entire group. +culling: + for { + for idx := len(*group) - 1; idx > 0; idx-- { + if (*group)[idx] == nil { + *group = (*group)[:idx] + continue culling + } + } + + break culling // encountered no new nil layers + } +} + +func (l *ds1Layers) Size() (w, h int) { + l.ensureInit() + l.cull() + + return l.width, l.height +} + +func (l *ds1Layers) SetSize(w, h int) { + l.width, l.height = w, h + + l.enforceSize(floorLayerGroup) + l.enforceSize(wallLayerGroup) + l.enforceSize(orientationLayerGroup) + l.enforceSize(shadowLayerGroup) + l.enforceSize(substitutionLayerGroup) +} + +func (l *ds1Layers) enforceSize(t layerGroupType) { + l.ensureInit() + l.cull() + + var group *layerGroup + + switch t { + case floorLayerGroup: + group = &l.Floors + case wallLayerGroup: + group = &l.Walls + case orientationLayerGroup: + group = &l.Orientations + case shadowLayerGroup: + group = &l.Shadows + case substitutionLayerGroup: + group = &l.Substitutions + default: + return + } + + for idx := range *group { + (*group)[idx].SetSize(l.width, l.height) + } +} + +func (l *ds1Layers) Width() int { + w, _ := l.Size() + return w +} + +func (l *ds1Layers) SetWidth(w int) { + l.SetSize(w, l.height) +} + +func (l *ds1Layers) Height() int { + _, h := l.Size() + return h +} + +func (l *ds1Layers) SetHeight(h int) { + l.SetSize(l.width, h) +} + +// generic push func for all layer types +func (l *ds1Layers) push(t layerGroupType, layer *layer) { + l.ensureInit() + l.cull() + + var group *layerGroup + + var max int + + switch t { + case floorLayerGroup: + group = &l.Floors + max = maxFloorLayers + case wallLayerGroup: + group = &l.Walls + max = maxWallLayers + case orientationLayerGroup: + group = &l.Orientations + max = maxOrientationLayers + case shadowLayerGroup: + group = &l.Shadows + max = maxShadowLayers + case substitutionLayerGroup: + group = &l.Substitutions + max = maxSubstitutionLayers + default: + return + } + + if len(*group) < max { + *group = append(*group, layer) + } +} + +// generic pop func for all layer types +func (l *ds1Layers) pop(t layerGroupType) *layer { + l.ensureInit() + l.cull() + + var group *layerGroup + + var theLayer *layer + + switch t { + case floorLayerGroup: + group = &l.Floors + case wallLayerGroup: + group = &l.Walls + case orientationLayerGroup: + group = &l.Orientations + case shadowLayerGroup: + group = &l.Shadows + case substitutionLayerGroup: + group = &l.Substitutions + default: + return nil + } + + // remove last layer of slice and return it + if len(*group) > 0 { + lastIdx := len(*group) - 1 + theLayer = (*group)[lastIdx] + *group = (*group)[:lastIdx] + + return theLayer + } + + return nil +} + +func (l *ds1Layers) get(t layerGroupType, idx int) *layer { + l.ensureInit() + l.cull() + + var group *layerGroup + + switch t { + case floorLayerGroup: + group = &l.Floors + case wallLayerGroup: + group = &l.Walls + case orientationLayerGroup: + group = &l.Orientations + case shadowLayerGroup: + group = &l.Shadows + case substitutionLayerGroup: + group = &l.Substitutions + default: + return nil + } + + if idx >= len(*group) || idx < 0 { + return nil + } + + return (*group)[idx] +} + +func (l *ds1Layers) insert(t layerGroupType, idx int, newLayer *layer) { + l.ensureInit() + l.cull() + + if newLayer == nil { + return + } + + var group layerGroup + + switch t { + case floorLayerGroup: + group = l.Floors + case wallLayerGroup: + group = l.Walls + case orientationLayerGroup: + group = l.Orientations + case shadowLayerGroup: + group = l.Shadows + case substitutionLayerGroup: + group = l.Substitutions + default: + return + } + + if len(group) == 0 { + group = append(group, newLayer) // nolint:staticcheck // we possibly use group later + return + } + + if idx > len(group)-1 { + idx = len(group) - 1 + } + + // example: + // suppose + // idx=1 + // newLayer=c + // existing layerGroup is [a, b] + group = append(group, group[idx:]...) // [a, b] becomes [a, b, b] + group[idx] = newLayer // [a, b, b] becomes [a, c, b] +} + +func (l *ds1Layers) delete(t layerGroupType, idx int) { + l.ensureInit() + l.cull() + + var group layerGroup + + switch t { + case floorLayerGroup: + group = l.Floors + case wallLayerGroup: + group = l.Walls + case orientationLayerGroup: + group = l.Orientations + case shadowLayerGroup: + group = l.Shadows + case substitutionLayerGroup: + group = l.Substitutions + default: + return + } + + if idx >= len(group) || idx < 0 { + return + } + + group[idx] = nil + + l.cull() +} + +func (l *ds1Layers) GetFloor(idx int) *layer { + return l.get(floorLayerGroup, idx) +} + +func (l *ds1Layers) PushFloor(floor *layer) *ds1Layers { + l.push(floorLayerGroup, floor) + return l +} + +func (l *ds1Layers) PopFloor() *layer { + return l.pop(floorLayerGroup) +} + +func (l *ds1Layers) InsertFloor(idx int, newFloor *layer) { + l.insert(floorLayerGroup, idx, newFloor) +} + +func (l *ds1Layers) DeleteFloor(idx int) { + l.delete(floorLayerGroup, idx) +} + +func (l *ds1Layers) GetWall(idx int) *layer { + return l.get(wallLayerGroup, idx) +} + +func (l *ds1Layers) PushWall(wall *layer) *ds1Layers { + l.push(wallLayerGroup, wall) + return l +} + +func (l *ds1Layers) PopWall() *layer { + return l.pop(wallLayerGroup) +} + +func (l *ds1Layers) InsertWall(idx int, newWall *layer) { + l.insert(wallLayerGroup, idx, newWall) +} + +func (l *ds1Layers) DeleteWall(idx int) { + l.delete(wallLayerGroup, idx) +} + +func (l *ds1Layers) GetOrientation(idx int) *layer { + return l.get(orientationLayerGroup, idx) +} + +func (l *ds1Layers) PushOrientation(orientation *layer) *ds1Layers { + l.push(orientationLayerGroup, orientation) + return l +} + +func (l *ds1Layers) PopOrientation() *layer { + return l.pop(orientationLayerGroup) +} + +func (l *ds1Layers) InsertOrientation(idx int, newOrientation *layer) { + l.insert(orientationLayerGroup, idx, newOrientation) +} + +func (l *ds1Layers) DeleteOrientation(idx int) { + l.delete(orientationLayerGroup, idx) +} + +func (l *ds1Layers) GetShadow(idx int) *layer { + return l.get(shadowLayerGroup, idx) +} + +func (l *ds1Layers) PushShadow(shadow *layer) *ds1Layers { + l.push(shadowLayerGroup, shadow) + return l +} + +func (l *ds1Layers) PopShadow() *layer { + return l.pop(shadowLayerGroup) +} + +func (l *ds1Layers) InsertShadow(idx int, newShadow *layer) { + l.insert(shadowLayerGroup, idx, newShadow) +} + +func (l *ds1Layers) DeleteShadow(idx int) { + l.delete(shadowLayerGroup, idx) +} + +func (l *ds1Layers) GetSubstitution(idx int) *layer { + return l.get(substitutionLayerGroup, idx) +} + +func (l *ds1Layers) PushSubstitution(sub *layer) *ds1Layers { + l.push(substitutionLayerGroup, sub) + return l +} + +func (l *ds1Layers) PopSubstitution() *layer { + return l.pop(substitutionLayerGroup) +} + +func (l *ds1Layers) InsertSubstitution(idx int, newSubstitution *layer) { + l.insert(shadowLayerGroup, idx, newSubstitution) +} + +func (l *ds1Layers) DeleteSubstitution(idx int) { + l.delete(shadowLayerGroup, idx) +} diff --git a/d2common/d2fileformats/d2ds1/ds1_layers_test.go b/d2common/d2fileformats/d2ds1/ds1_layers_test.go new file mode 100644 index 00000000..8a29e1f1 --- /dev/null +++ b/d2common/d2fileformats/d2ds1/ds1_layers_test.go @@ -0,0 +1,152 @@ +package d2ds1 + +import ( + "testing" +) + +func Test_ds1Layers_DeleteFloor(t *testing.T) {} + +func Test_ds1Layers_DeleteOrientation(t *testing.T) {} + +func Test_ds1Layers_DeleteShadow(t *testing.T) {} + +func Test_ds1Layers_DeleteSubstitution(t *testing.T) {} + +func Test_ds1Layers_DeleteWall(t *testing.T) {} + +func Test_ds1Layers_GetFloor(t *testing.T) {} + +func Test_ds1Layers_GetOrientation(t *testing.T) {} + +func Test_ds1Layers_GetShadow(t *testing.T) {} + +func Test_ds1Layers_GetSubstitution(t *testing.T) {} + +func Test_ds1Layers_GetWall(t *testing.T) {} + +func Test_ds1Layers_InsertFloor(t *testing.T) {} + +func Test_ds1Layers_InsertOrientation(t *testing.T) {} + +func Test_ds1Layers_InsertShadow(t *testing.T) {} + +func Test_ds1Layers_InsertSubstitution(t *testing.T) {} + +func Test_ds1Layers_InsertWall(t *testing.T) {} + +func Test_ds1Layers_PopFloor(t *testing.T) {} + +func Test_ds1Layers_PopOrientation(t *testing.T) {} + +func Test_ds1Layers_PopShadow(t *testing.T) {} + +func Test_ds1Layers_PopSubstitution(t *testing.T) {} + +func Test_ds1Layers_PopWall(t *testing.T) {} + +func Test_ds1Layers_Push(t *testing.T) { + t.Run("Floor", func(t *testing.T) { + test_ds1Layers_PushLayer(floorLayerGroup, t) + }) + + t.Run("Wall", func(t *testing.T) { + test_ds1Layers_PushLayer(wallLayerGroup, t) + }) + + t.Run("Orientation", func(t *testing.T) { + test_ds1Layers_PushLayer(orientationLayerGroup, t) + }) + + t.Run("Shadow", func(t *testing.T) { + test_ds1Layers_PushLayer(shadowLayerGroup, t) + }) + + t.Run("Substitution", func(t *testing.T) { + test_ds1Layers_PushLayer(substitutionLayerGroup, t) + }) +} + +// for all layer types, the test is the same +// when we push a layer, we expect an increment, and when we push a bunch of times, +// we expect to never exceed the max. we also expect to be able to retrieve a non-nil +// layer after we push. +func test_ds1Layers_PushLayer(lt layerGroupType, t *testing.T) { + layers := &ds1Layers{} + + // we need to set up some shit to handle the test in a generic way + var push func() + + var get func(idx int) *layer + + var max int + + var group *layerGroup + + check := func(expected int) { + actual := len(*group) + got := get(expected - 1) + + if actual != expected { + t.Fatalf("unexpected number of layers: expected %d, got %d", expected, actual) + } + + if got == nil { + t.Fatal("got nil layer") + } + } + + switch lt { + case floorLayerGroup: + push = func() { layers.PushFloor(&layer{}) } + get = layers.GetFloor + max = maxFloorLayers + group = &layers.Floors + case wallLayerGroup: + push = func() { layers.PushWall(&layer{}) } + get = layers.GetWall + max = maxWallLayers + group = &layers.Walls + case orientationLayerGroup: + push = func() { layers.PushOrientation(&layer{}) } + get = layers.GetOrientation + max = maxOrientationLayers + group = &layers.Orientations + case shadowLayerGroup: + push = func() { layers.PushShadow(&layer{}) } + get = layers.GetShadow + max = maxShadowLayers + group = &layers.Shadows + case substitutionLayerGroup: + push = func() { layers.PushSubstitution(&layer{}) } + get = layers.GetSubstitution + max = maxSubstitutionLayers + group = &layers.Substitutions + default: + t.Fatal("unknown layer type given") + } + + // push one time, we expect a single layer to exist + push() + check(1) + + // if we push a bunch of times, we expect to not exceed the max + push() + push() + push() + push() + push() + push() + push() + push() + push() + push() + check(max) +} + +func test_ds1Layers_PopLayer(lt layerGroupType, t *testing.T) {} + +func test_ds1Layers_InsertLayer(lt layerGroupType, t *testing.T) {} + +func test_ds1Layers_DeleteLayer(lt layerGroupType, t *testing.T) {} + +func test_ds1Layers_GetLayer(lt layerGroupType, t *testing.T) {} diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index c56e2f32..f69ef7f1 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -1,665 +1 @@ package d2ds1 - -import ( - "testing" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" -) - -func exampleDS1() *DS1 { - return &DS1{ - files: []string{"a.dt1", "b.dt1"}, - objects: []Object{ - {0, 0, 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{{}}, - }, - }, - }, - substitutionGroups: nil, - version: 17, - width: 2, - height: 2, - act: 1, - substitutionType: 0, - numberOfWallLayers: 1, - numberOfFloorLayers: 1, - numberOfShadowLayers: 1, - numberOfSubstitutionLayers: 1, - } -} - -// checks, if DS1 structure could be marshaled and unmarshaled -func testIfRestorable(ds1 *DS1, test func(ds1 *DS1)) error { - test(ds1) - - var err error - - data := ds1.Marshal() - - newDS1, err := LoadDS1(data) - if err != nil { - return err - } - - test(newDS1) - - return nil -} - -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 marshaled ds1 data") - return - } - - 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) { - ds1 := exampleDS1() - - files := ds1.Files() - - for idx := range files { - if ds1.files[idx] != files[idx] { - t.Error("unexpected files from ds1") - } - } -} - -func TestDS1_AddFile(t *testing.T) { - ds1 := exampleDS1() - - numBefore := len(ds1.files) - - ds1.AddFile("other.ds1") - - numAfter := len(ds1.files) - - test := func(ds1 *DS1) { - if (numBefore + 1) != numAfter { - t.Error("unexpected number of files in ds1") - } - } - - if err := testIfRestorable(ds1, test); err != nil { - t.Errorf("unable to restore: %v", err) - } -} - -func TestDS1_RemoveFile(t *testing.T) { - ds1 := exampleDS1() - - 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") - } - - 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, test); 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) - - test := func(ds1 *DS1) { - if (numBefore + 1) != numAfter { - t.Error("unexpected number of objects in ds1") - } - } - - if err := testIfRestorable(ds1, test); 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) - - test := func(ds1 *DS1) { - if len(ds1.objects) == numBefore { - t.Error("did not remove object when expected") - } - } - - if err := testIfRestorable(ds1, test); 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{ - {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}, - }, - } - - exampleTile2 := Tile{ - Floors: []floorShadow{ - {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}, - }, - Shadows: []floorShadow{ - {2, 4, 5, 33, 6, 7, 0, false, 1024}, - }, - } - - tiles := [][]Tile{{exampleTile1, exampleTile2}, {exampleTile2, exampleTile1}} - - ds1.SetTiles(tiles) - - 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 err := testIfRestorable(ds1, test); err != nil { - t.Errorf("unable to restore: %v", err) - } -} - -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, 0, 0, true, 1024}, - }, - 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}, - }, - } - - ds1.SetTile(0, 0, &exampleTile) - - test := func(ds1 *DS1) { - if ds1.tiles[0][0].Floors[0].Prop1 != exampleTile.Floors[0].Prop1 { - t.Fatal("unexpected tile was set") - } - - if ds1.tiles[0][0].Walls[0].Zero != exampleTile.Walls[0].Zero { - 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, test); 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) - - test := func(ds1 *DS1) { - if newVersion != int(ds1.version) { - t.Fatal("ds1.SetVersion set version incorrectly") - } - } - - if err := testIfRestorable(ds1, test); 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)) - - test := func(ds1 *DS1) { - if newWidth != ds1.width { - t.Fatal("unexpected width after set") - } - } - - if err := testIfRestorable(ds1, test); err != nil { - t.Errorf("unable to restore: %v", err) - } -} - -func TestDS1_Height(t *testing.T) { - ds1 := exampleDS1() - - if int(ds1.height) != ds1.Height() { - t.Error("unexpected height") - } -} - -func TestDS1_SetHeight(t *testing.T) { - ds1 := exampleDS1() - - var newHeight int32 = 5 - - ds1.SetHeight(int(newHeight)) - - test := func(ds1 *DS1) { - if newHeight != ds1.height { - t.Fatal("unexpected heigth after set") - } - } - - if err := testIfRestorable(ds1, test); 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 := 5 - - ds1.SetAct(nice) - - test := func(ds1 *DS1) { - if int(ds1.act) != nice { - t.Error("unexpected value for act") - } - } - - if err := testIfRestorable(ds1, test); 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") - } -} - -func TestDS1_SetNumberOfWalls(t *testing.T) { - var err error - - ds1 := exampleDS1() - - newNumber := int32(4) - - 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) { - 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 - - 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) { - 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() - - if ds1.NumberOfFloorLayers() != int(ds1.numberOfFloorLayers) { - t.Error("unexpected number of floor layers") - } -} - -func TestDS1_SetNumberOfFloors(t *testing.T) { - var err error - - ds1 := exampleDS1() - - newNumber := int32(2) - - 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) { - 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 - - 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) { - 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() - - 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_SubstitutionGroups(t *testing.T) { - ds1 := exampleDS1() - - sg := ds1.SubstitutionGroups() - - for i := 0; i < len(ds1.substitutionGroups); i++ { - if sg[i] != ds1.substitutionGroups[i] { - t.Fatal("unexpected substitution group returned") - } - } -} - -func TestDS1_SetSubstitutionGroups(t *testing.T) { - ds1 := exampleDS1() - - newGroup := []SubstitutionGroup{ - { - TileX: 20, - TileY: 12, - WidthInTiles: 212, - HeightInTiles: 334, - Unknown: 1024, - }, - } - - ds1.SetSubstitutionGroups(newGroup) - - if ds1.substitutionGroups[0] != newGroup[0] { - t.Fatal("unexpected substitution group added") - } -} - -func TestDS1_GetStreamLayerTypes(t *testing.T) { - ds1 := exampleDS1() - - lst := []d2enum.LayerStreamType{ - d2enum.LayerStreamWall1, - d2enum.LayerStreamOrientation1, - d2enum.LayerStreamFloor1, - d2enum.LayerStreamShadow, - d2enum.LayerStreamSubstitute, - } - - layerStreamTypes := ds1.GetStreamLayerTypes() - - if len(lst) != len(layerStreamTypes) { - t.Fatal("unexpected length") - } - - for i := range lst { - if lst[i] != layerStreamTypes[i] { - t.Fatal("Unexpected type was set") - } - } -} diff --git a/d2common/d2fileformats/d2ds1/ds1_version.go b/d2common/d2fileformats/d2ds1/ds1_version.go new file mode 100644 index 00000000..6494a787 --- /dev/null +++ b/d2common/d2fileformats/d2ds1/ds1_version.go @@ -0,0 +1,59 @@ +package d2ds1 + +type ds1version int + +const ( + v3 ds1version = 3 + v4 = 4 + v7 = 7 + v8 = 8 + v9 = 9 + v10 = 10 + v12 = 12 + v13 = 13 + v14 = 14 + v15 = 15 + v16 = 16 + v18 = 18 +) + +func (v ds1version) hasUnknown1Bytes() bool { + // just after the header will be some meaningless (?) bytes + return v >= v9 && v <= v13 +} + +func (v ds1version) hasUnknown2Bytes() bool { + return v >= v18 +} + +func (v ds1version) specifiesAct() bool { + // in the header + return v >= v8 +} + +func (v ds1version) specifiesSubstitutionType() bool { + // in the header + return v >= v10 +} + +func (v ds1version) specifiesWalls() bool { + // just after header, specifies number of Walls + return v >= v4 +} + +func (v ds1version) specifiesFloors() bool { + // just after header, specifies number of Floors + return v >= v16 +} + +func (v ds1version) hasFileList() bool { + return v >= v3 +} + +func (v ds1version) hasObjects() bool { + return v >= v3 +} + +func (v ds1version) hasSubstitutions() bool { + return v >= v12 +} diff --git a/d2common/d2fileformats/d2ds1/floor_shadow.go b/d2common/d2fileformats/d2ds1/floor_shadow.go deleted file mode 100644 index 36130d39..00000000 --- a/d2common/d2fileformats/d2ds1/floor_shadow.go +++ /dev/null @@ -1,74 +0,0 @@ -package d2ds1 - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" -) - -const ( - prop1Bitmask = 0x000000FF - prop1Offset = 0 - prop1Length = 8 - - sequenceBitmask = 0x00003F00 - sequenceOffset = 8 - sequenceLength = 6 - - unknown1Bitmask = 0x000FC000 - unknown1Offset = 14 - unknown1Length = 6 - - styleBitmask = 0x03F00000 - styleOffset = 20 - styleLength = 6 - - unknown2Bitmask = 0x7C000000 - unknown2Offset = 26 - unknown2Length = 5 - - hiddenBitmask = 0x80000000 - hiddenOffset = 31 - hiddenLength = 1 -) - -type floorShadow struct { - Prop1 byte - Sequence byte - Unknown1 byte - Style byte - Unknown2 byte - HiddenBytes byte - RandomIndex byte - Animated bool - 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 *Floor) Hidden() bool { - return f.HiddenBytes > 0 -} - -// Decode decodes floor-shadow record -func (f *floorShadow) Decode(dw uint32) { - f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) - f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) - f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) - f.Style = byte((dw & styleBitmask) >> styleOffset) - f.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) - f.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset) -} - -// Encode adds Floor's bits to stream writter given -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) - sw.PushBits32(uint32(f.Style), styleLength) - sw.PushBits32(uint32(f.Unknown2), unknown2Length) - sw.PushBits32(uint32(f.HiddenBytes), hiddenLength) -} diff --git a/d2common/d2fileformats/d2ds1/layer.go b/d2common/d2fileformats/d2ds1/layer.go new file mode 100644 index 00000000..d1d2ac8c --- /dev/null +++ b/d2common/d2fileformats/d2ds1/layer.go @@ -0,0 +1,143 @@ +package d2ds1 + +// layerStreamType represents a layer stream type +type layerStreamType int + +// Layer stream types +const ( + layerStreamWall1 layerStreamType = iota + layerStreamWall2 + layerStreamWall3 + layerStreamWall4 + layerStreamOrientation1 + layerStreamOrientation2 + layerStreamOrientation3 + layerStreamOrientation4 + layerStreamFloor1 + layerStreamFloor2 + layerStreamShadow1 + layerStreamSubstitute1 +) + +type tileRow []Tile // index is x coordinate +type tileGrid []tileRow // index is y coordinate + +type grid interface { + Width() int + SetWidth(w int) grid + + Height() int + SetHeight(h int) grid + + Size() (w, h int) + SetSize(w, h int) grid + + Tile(x, y int) *Tile + SetTile(x, y int, t *Tile) +} + +type layer struct { + tiles tileGrid +} + +func (l *layer) Tile(x, y int) *Tile { + if l.Width() < x || l.Height() < y { + return nil + } + + return &l.tiles[y][x] +} + +func (l *layer) SetTile(x, y int, t *Tile) { + if l.Width() > x || l.Height() > y { + return + } + + l.tiles[y][x] = *t +} + +func (l *layer) Width() int { + if len(l.tiles[0]) < 1 { + l.SetWidth(1) + } + + return len(l.tiles[0]) +} + +func (l *layer) SetWidth(w int) grid { + if w < 1 { + w = 1 + } + + // ensure at least one row + if len(l.tiles) < 1 { + l.tiles = make(tileGrid, 1) + } + + // create/copy tiles as required to satisfy width + for y := range l.tiles { + if (w - len(l.tiles[y])) == 0 { // if requested width same as row width + continue + } + + tmpRow := make(tileRow, w) + + for x := range tmpRow { + if x < len(l.tiles[y]) { // if tile exists + tmpRow[x] = l.tiles[y][x] // copy it + } + } + + l.tiles[y] = tmpRow + } + + return l +} + +func (l *layer) Height() int { + if len(l.tiles) < 1 { + l.SetHeight(1) + } + + return len(l.tiles) +} + +func (l *layer) SetHeight(h int) grid { + if h < 1 { + h = 1 + } + + // make tmpGrid to move existing tiles into + tmpGrid := make(tileGrid, h) + + for y := range tmpGrid { + tmpGrid[y] = make(tileRow, l.Width()) + } + + // move existing tiles over + for y := range l.tiles { + if y >= len(tmpGrid) { + continue + } + + for x := range l.tiles[y] { + if x >= len(tmpGrid[y]) { + continue + } + + tmpGrid[y][x] = l.tiles[y][x] + } + } + + l.tiles = tmpGrid + + return l +} + +func (l *layer) Size() (w, h int) { + return l.Width(), l.Height() +} + +func (l *layer) SetSize(w, h int) grid { + return l.SetWidth(w).SetHeight(h) +} diff --git a/d2common/d2fileformats/d2ds1/layer_test.go b/d2common/d2fileformats/d2ds1/layer_test.go new file mode 100644 index 00000000..92c37ea6 --- /dev/null +++ b/d2common/d2fileformats/d2ds1/layer_test.go @@ -0,0 +1,29 @@ +package d2ds1 + +import "testing" + +func Test_layers(t *testing.T) { + const ( + fmtWidthHeightError = "unexpected wall layer width/height: %dx%d" + ) + + l := &layer{} + + l.SetSize(0, 0) + + if l.Width() != 1 || l.Height() != 1 { + t.Fatalf(fmtWidthHeightError, l.Width(), l.Height()) + } + + l.SetSize(4, 5) + + if l.Width() != 4 || l.Height() != 5 { + t.Fatalf(fmtWidthHeightError, l.Width(), l.Height()) + } + + l.SetSize(4, 3) + + if l.Width() != 4 || l.Height() != 3 { + t.Fatalf(fmtWidthHeightError, l.Width(), l.Height()) + } +} diff --git a/d2common/d2fileformats/d2ds1/substitution.go b/d2common/d2fileformats/d2ds1/substitution.go deleted file mode 100644 index 0c5d8542..00000000 --- a/d2common/d2fileformats/d2ds1/substitution.go +++ /dev/null @@ -1,6 +0,0 @@ -package d2ds1 - -// Substitution represents a substitution record in a DS1 file. -type Substitution struct { - Unknown uint32 -} diff --git a/d2common/d2fileformats/d2ds1/tile.go b/d2common/d2fileformats/d2ds1/tile.go index 611ae49d..42726d31 100644 --- a/d2common/d2fileformats/d2ds1/tile.go +++ b/d2common/d2fileformats/d2ds1/tile.go @@ -1,18 +1,123 @@ package d2ds1 +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +const ( + prop1Bitmask = 0x000000FF + prop1Offset = 0 + prop1Length = 8 + + sequenceBitmask = 0x00003F00 + sequenceOffset = 8 + sequenceLength = 6 + + unknown1Bitmask = 0x000FC000 + unknown1Offset = 14 + unknown1Length = 6 + + styleBitmask = 0x03F00000 + styleOffset = 20 + styleLength = 6 + + unknown2Bitmask = 0x7C000000 + unknown2Offset = 26 + unknown2Length = 5 + + hiddenBitmask = 0x80000000 + hiddenOffset = 31 + hiddenLength = 1 +) + +type tileCommonFields struct { + Type d2enum.TileType + Zero byte + Prop1 byte + Sequence byte + Unknown1 byte + Style byte + Unknown2 byte + HiddenBytes byte + RandomIndex byte + YAdjust int +} + +type tileFloorShadowFields struct { + Animated bool +} + +type tileSubstitutionFields struct { + Substitution uint32 // unknown +} + // 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 + tileCommonFields + tileFloorShadowFields + tileSubstitutionFields } -func makeDefaultTile() Tile { - return Tile{ - Floors: []Floor{{}}, - Walls: []Wall{{}}, - Shadows: []Shadow{{}}, - Substitutions: []Substitution{{}}, - } +// Hidden returns if wall is hidden +func (t *Tile) Hidden() bool { + return t.HiddenBytes > 0 +} + +// DecodeWall decodes as a wall record +func (t *Tile) DecodeWall(dw uint32) { + t.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) + t.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) + t.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) + t.Style = byte((dw & styleBitmask) >> styleOffset) + t.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) + t.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset) +} + +// EncodeWall adds wall's record's bytes into stream writer given +func (t *Tile) EncodeWall(sw *d2datautils.StreamWriter) { + sw.PushBits32(uint32(t.Prop1), prop1Length) + sw.PushBits32(uint32(t.Sequence), sequenceLength) + sw.PushBits32(uint32(t.Unknown1), unknown1Length) + sw.PushBits32(uint32(t.Style), styleLength) + sw.PushBits32(uint32(t.Unknown2), unknown2Length) + sw.PushBits32(uint32(t.HiddenBytes), hiddenLength) +} + +func (t *Tile) decodeFloorShadow(dw uint32) { + t.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) + t.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) + t.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) + t.Style = byte((dw & styleBitmask) >> styleOffset) + t.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) + t.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset) +} + +func (t *Tile) encodeFloorShadow(sw *d2datautils.StreamWriter) { + sw.PushBits32(uint32(t.Prop1), prop1Length) + sw.PushBits32(uint32(t.Sequence), sequenceLength) + sw.PushBits32(uint32(t.Unknown1), unknown1Length) + sw.PushBits32(uint32(t.Style), styleLength) + sw.PushBits32(uint32(t.Unknown2), unknown2Length) + sw.PushBits32(uint32(t.HiddenBytes), hiddenLength) +} + +// DecodeFloor decodes as a floor record +func (t *Tile) DecodeFloor(dw uint32) { + t.decodeFloorShadow(dw) +} + +// EncodeFloor adds Floor's bits to stream writer given +func (t *Tile) EncodeFloor(sw *d2datautils.StreamWriter) { + t.encodeFloorShadow(sw) +} + +// DecodeShadow decodes as a shadow record +func (t *Tile) DecodeShadow(dw uint32) { + t.decodeFloorShadow(dw) +} + +// EncodeShadow adds shadow's bits to stream writer given +func (t *Tile) EncodeShadow(sw *d2datautils.StreamWriter) { + t.encodeFloorShadow(sw) } diff --git a/d2common/d2fileformats/d2ds1/wall.go b/d2common/d2fileformats/d2ds1/wall.go deleted file mode 100644 index 5123e9f7..00000000 --- a/d2common/d2fileformats/d2ds1/wall.go +++ /dev/null @@ -1,45 +0,0 @@ -package d2ds1 - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// Wall represents a wall record. -type Wall struct { - Type d2enum.TileType - Zero byte - Prop1 byte - Sequence byte - Unknown1 byte - Style byte - Unknown2 byte - HiddenBytes byte - RandomIndex byte - YAdjust int -} - -// Hidden returns if wall is hidden -func (w *Wall) Hidden() bool { - return w.HiddenBytes > 0 -} - -// Decode decodes wall record -func (w *Wall) Decode(dw uint32) { - w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) - w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) - w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) - w.Style = byte((dw & styleBitmask) >> styleOffset) - w.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) - w.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset) -} - -// Encode adds wall's record's bytes into stream writer given -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) - sw.PushBits32(uint32(w.Style), styleLength) - sw.PushBits32(uint32(w.Unknown2), unknown2Length) - sw.PushBits32(uint32(w.HiddenBytes), hiddenLength) -} diff --git a/d2core/d2asset/asset_manager.go b/d2core/d2asset/asset_manager.go index a5ac1467..702d17ec 100644 --- a/d2core/d2asset/asset_manager.go +++ b/d2core/d2asset/asset_manager.go @@ -517,7 +517,7 @@ func (am *AssetManager) LoadDS1(ds1Path string) (*d2ds1.DS1, error) { return nil, err } - ds1, err := d2ds1.LoadDS1(fileData) + ds1, err := d2ds1.Unmarshal(fileData) if err != nil { return nil, err } diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 0fc01256..981cbd4c 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 e01bb0e5..cdf4feb2 100644 --- a/d2core/d2map/d2mapengine/map_tile.go +++ b/d2core/d2map/d2mapengine/map_tile.go @@ -2,13 +2,14 @@ package d2mapengine import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" ) // MapTile is a tile placed on the map type MapTile struct { - Components d2ds1.Tile + Components d2mapstamp.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 e1d78176..74b108f5 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -284,7 +284,9 @@ 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.Floor{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass + floorTile := d2ds1.Tile{} + floorTile.Prop1 = 1 + tile.Components.Floors = []d2ds1.Tile{floorTile} // wildernessGrass tile.PrepareTile(x, y, g.engine) } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 362aab8a..3b58249e 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.Floor, target d2interface.Surface) { +func (mr *MapRenderer) renderFloor(tile d2ds1.Tile, 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.Floor, target d2interface.Surface) target.Render(img) } -func (mr *MapRenderer) renderWall(tile d2ds1.Wall, viewport *Viewport, target d2interface.Surface) { +func (mr *MapRenderer) renderWall(tile d2ds1.Tile, 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.Wall, viewport *Viewport, target d2 target.Render(img) } -func (mr *MapRenderer) renderShadow(tile d2ds1.Shadow, target d2interface.Surface) { +func (mr *MapRenderer) renderShadow(tile d2ds1.Tile, 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 7ac05191..6569d9dd 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.Floor) { +func (mr *MapRenderer) generateFloorCache(tile *d2ds1.Tile) { 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.Floor) { } } -func (mr *MapRenderer) generateShadowCache(tile *d2ds1.Shadow) { +func (mr *MapRenderer) generateShadowCache(tile *d2ds1.Tile) { 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.Shadow) { mr.setImageCacheRecord(tile.Style, tile.Sequence, d2enum.TileShadow, tile.RandomIndex, image) } -func (mr *MapRenderer) generateWallCache(tile *d2ds1.Wall) { +func (mr *MapRenderer) generateWallCache(tile *d2ds1.Tile) { tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), tile.Type) var tileData *d2dt1.Tile diff --git a/d2core/d2map/d2mapstamp/factory.go b/d2core/d2map/d2mapstamp/factory.go index f8072509..d350a597 100644 --- a/d2core/d2map/d2mapstamp/factory.go +++ b/d2core/d2map/d2mapstamp/factory.go @@ -87,7 +87,7 @@ func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fil panic(err) } - stamp.ds1, err = d2ds1.LoadDS1(fileData) + stamp.ds1, err = d2ds1.Unmarshal(fileData) if err != nil { f.Error(err.Error()) return nil diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index ec1ef92a..e268aadf 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -54,9 +54,45 @@ func (mr *Stamp) RegionPath() string { return mr.regionPath } +type Tile struct { + Walls []d2ds1.Tile + Orientations []d2ds1.Tile + Floors []d2ds1.Tile + Shadows []d2ds1.Tile + Substitutions []d2ds1.Tile +} + // Tile returns the tile at the given x and y tile coordinates. -func (mr *Stamp) Tile(x, y int) *d2ds1.Tile { - return mr.ds1.Tile(x, y) +func (mr *Stamp) Tile(x, y int) *Tile { + t := &Tile{ + Walls: make([]d2ds1.Tile, len(mr.ds1.Walls)), + Orientations: make([]d2ds1.Tile, len(mr.ds1.Orientations)), + Floors: make([]d2ds1.Tile, len(mr.ds1.Floors)), + Shadows: make([]d2ds1.Tile, len(mr.ds1.Shadows)), + Substitutions: make([]d2ds1.Tile, len(mr.ds1.Substitutions)), + } + + for idx := range mr.ds1.Walls { + t.Walls[idx] = *mr.ds1.Walls[idx].Tile(x, y) + } + + for idx := range mr.ds1.Orientations { + t.Orientations[idx] = *mr.ds1.Orientations[idx].Tile(x, y) + } + + for idx := range mr.ds1.Floors { + t.Floors[idx] = *mr.ds1.Floors[idx].Tile(x, y) + } + + for idx := range mr.ds1.Shadows { + t.Shadows[idx] = *mr.ds1.Shadows[idx].Tile(x, y) + } + + for idx := range mr.ds1.Substitutions { + t.Substitutions[idx] = *mr.ds1.Substitutions[idx].Tile(x, y) + } + + return t } // TileData returns the tile data for the tile with given style, sequence and type. @@ -75,9 +111,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[int32(mr.ds1.Act())][object.ID] + monPreset := mr.factory.asset.Records.Monster.Presets[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 +133,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(mr.ds1.Act(), object.Type, object.ID) + lookup := mr.factory.asset.Records.LookupObject(int(mr.ds1.Act), object.Type, object.ID) if lookup == nil { continue diff --git a/et b/et new file mode 100644 index 00000000..3237be5c --- /dev/null +++ b/et @@ -0,0 +1,73 @@ +diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go +index 23035a50..d17527f0 100644 +--- a/d2common/d2fileformats/d2ds1/ds1_test.go ++++ b/d2common/d2fileformats/d2ds1/ds1_test.go +@@ -575,6 +575,24 @@ func TestDS1_SetNumberOfFloors(t *testing.T) { + if err := testIfRestorable(ds1, test); err != nil { + t.Errorf("unable to restore: %v", err) + } ++ ++ newNumber = 10 ++ ++ 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) { +diff --git a/d2common/d2fileformats/d2ds1/layers.go b/d2common/d2fileformats/d2ds1/layers.go +index f69ef7f1..b0488a34 100644 +--- a/d2common/d2fileformats/d2ds1/layers.go ++++ b/d2common/d2fileformats/d2ds1/layers.go +@@ -1 +1,39 @@ + package d2ds1 ++ ++const ( ++ maxWalls = 4 ++) ++ ++type WallLayer [][]*Wall ++type FloorLayer [][]*Floor ++type ShadowLayer [][]*Shadow ++ ++type layers struct { ++ Walls []WallLayer ++ Floors []FloorLayer ++ Shadows []ShadowLayer ++} ++ ++func (l *layers) PushWallLayer() { ++ ++} ++ ++func (l *layers) PopWallLayer() WallLayer { ++ ++} ++ ++func (l *layers) PushFloorLayer() { ++ ++} ++ ++func (l *layers) PopFloorLayer() FloorLayer { ++ ++} ++ ++func (l *layers) PushShadowLayer() { ++ ++} ++ ++func (l *layers) PopShadowLayer() ShadowLayer { ++ ++} From 6f41387e308708aa6dee10184a12650e44f0c145 Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 3 Mar 2021 00:20:44 +0100 Subject: [PATCH 20/33] Ds1 refactor - tests (#10) * ds1 refactor test: example data * added loader check * ds1 refactor: fixed bug, with loading substitutions; added descriptive error message in engine.go:118 and changed Logger.Error with Logger.Fatal * ds1 refactor: fixed loading bug * ds1 refactor: fixed bug when walls wasn't rendered (now we can see only walls :-) Co-authored-by: M. Sz --- d2common/d2fileformats/d2ds1/ds1.go | 8 +- d2common/d2fileformats/d2ds1/ds1_test.go | 221 +++++++++++++++++++++++ d2common/d2fileformats/d2ds1/tile.go | 8 +- d2core/d2asset/asset_manager.go | 2 +- d2core/d2map/d2mapengine/engine.go | 2 +- 5 files changed, 233 insertions(+), 8 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index e13e5890..1669448a 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -42,7 +42,7 @@ type DS1 struct { const ( defaultNumFloors = 1 defaultNumShadows = maxShadowLayers - defaultNumSubstitutions = maxSubstitutionLayers + defaultNumSubstitutions = 0 ) // Unmarshal the given bytes to a DS1 struct @@ -154,8 +154,8 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { return fmt.Errorf("reading height: %v", err) } - //width++ - //height++ + width++ + height++ ds1.SetSize(int(width), int(height)) @@ -506,7 +506,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } } - tile := ds1.Orientations[wallIndex].Tile(x, y) + tile := ds1.Walls[wallIndex].Tile(x, y) tile.Type = d2enum.TileType(c) tile.Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset) case layerStreamFloor1, layerStreamFloor2: diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index f69ef7f1..afc0ca0e 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -1 +1,222 @@ package d2ds1 + +import ( + "testing" + + "log" + "os" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" +) + +func exampleData() *DS1 { + exampleFloor1 := Tile{ + // common fields + tileCommonFields: tileCommonFields{ + Prop1: 2, + Sequence: 89, + Unknown1: 123, + Style: 20, + Unknown2: 53, + HiddenBytes: 1, + RandomIndex: 2, + YAdjust: 21, + }, + tileFloorShadowFields: tileFloorShadowFields{ + Animated: false, + }, + } + + exampleFloor2 := Tile{ + // common fields + tileCommonFields: tileCommonFields{ + Prop1: 3, + Sequence: 89, + Unknown1: 213, + Style: 28, + Unknown2: 53, + HiddenBytes: 7, + RandomIndex: 3, + YAdjust: 28, + }, + tileFloorShadowFields: tileFloorShadowFields{ + Animated: true, + }, + } + + exampleWall1 := Tile{ + // common fields + tileCommonFields: tileCommonFields{ + Prop1: 3, + Sequence: 89, + Unknown1: 213, + Style: 28, + Unknown2: 53, + HiddenBytes: 7, + RandomIndex: 3, + YAdjust: 28, + }, + tileWallFields: tileWallFields{ + Type: d2enum.TileRightWall, + }, + } + + exampleWall2 := Tile{ + // common fields + tileCommonFields: tileCommonFields{ + Prop1: 3, + Sequence: 93, + Unknown1: 193, + Style: 17, + Unknown2: 13, + HiddenBytes: 1, + RandomIndex: 1, + YAdjust: 22, + }, + tileWallFields: tileWallFields{ + Type: d2enum.TileLeftWall, + }, + } + + exampleShadow := Tile{ + // common fields + tileCommonFields: tileCommonFields{ + Prop1: 3, + Sequence: 93, + Unknown1: 173, + Style: 17, + Unknown2: 12, + HiddenBytes: 1, + RandomIndex: 1, + YAdjust: 22, + }, + tileFloorShadowFields: tileFloorShadowFields{ + Animated: false, + }, + } + + result := &DS1{ + ds1Layers: &ds1Layers{ + width: 20, + height: 80, + Floors: layerGroup{ + // number of floors (one floor) + { + // tile grid = []tileRow + tiles: tileGrid{ + // tile rows = []Tile + // 2x2 tiles + { + exampleFloor1, + exampleFloor2, + }, + { + exampleFloor2, + exampleFloor1, + }, + }, + }, + }, + Walls: layerGroup{ + // number of walls (two floors) + { + // tile grid = []tileRow + tiles: tileGrid{ + // tile rows = []Tile + // 2x2 tiles + { + exampleWall1, + exampleWall2, + }, + { + exampleWall2, + exampleWall1, + }, + }, + }, + { + // tile grid = []tileRow + tiles: tileGrid{ + // tile rows = []Tile + // 2x2 tiles + { + exampleWall1, + exampleWall2, + }, + { + exampleWall2, + exampleWall1, + }, + }, + }, + }, + Shadows: layerGroup{ + // number of shadows (always 1) + { + // tile grid = []tileRow + tiles: tileGrid{ + // tile rows = []Tile + // 2x2 tiles + { + exampleShadow, + exampleShadow, + }, + { + exampleShadow, + exampleShadow, + }, + }, + }, + }, + }, + Files: []string{"a.dt1", "bfile.dt1"}, + Objects: []Object{ + {0, 0, 0, 0, 0, nil}, + {0, 1, 0, 0, 0, []d2path.Path{{}}}, + {0, 2, 0, 0, 0, nil}, + {0, 3, 0, 0, 0, nil}, + }, + substitutionGroups: nil, + version: 17, + Act: 1, + substitutionType: 0, + unknown1: make([]byte, 8), + unknown2: 20, + } + + return result +} + +func TestDS1_Load(t *testing.T) { + testFile, fileErr := os.Open("testdata/testdata.ds1") + if fileErr != nil { + t.Error("cannot open test data file") + return + } + + data := make([]byte, 0) + buf := make([]byte, 16) + + for { + numRead, err := testFile.Read(buf) + + data = append(data, buf[:numRead]...) + + if err != nil { + break + } + } + + _, loadErr := Unmarshal(data) + if loadErr != nil { + t.Error(loadErr) + } + + err := testFile.Close() + if err != nil { + t.Fail() + log.Print(err) + } + +} diff --git a/d2common/d2fileformats/d2ds1/tile.go b/d2common/d2fileformats/d2ds1/tile.go index 42726d31..a8077536 100644 --- a/d2common/d2fileformats/d2ds1/tile.go +++ b/d2common/d2fileformats/d2ds1/tile.go @@ -32,8 +32,6 @@ const ( ) type tileCommonFields struct { - Type d2enum.TileType - Zero byte Prop1 byte Sequence byte Unknown1 byte @@ -52,11 +50,17 @@ type tileSubstitutionFields struct { Substitution uint32 // unknown } +type tileWallFields struct { + Type d2enum.TileType + Zero byte +} + // Tile represents a tile record in a DS1 file. type Tile struct { tileCommonFields tileFloorShadowFields tileSubstitutionFields + tileWallFields } // Hidden returns if wall is hidden diff --git a/d2core/d2asset/asset_manager.go b/d2core/d2asset/asset_manager.go index 702d17ec..04771293 100644 --- a/d2core/d2asset/asset_manager.go +++ b/d2core/d2asset/asset_manager.go @@ -519,7 +519,7 @@ func (am *AssetManager) LoadDS1(ds1Path string) (*d2ds1.DS1, error) { ds1, err := d2ds1.Unmarshal(fileData) if err != nil { - return nil, err + return nil, fmt.Errorf("loading ds1 file %s: %v", "/data/global/tiles"+ds1Path, err) } if err := am.dt1s.Insert(ds1Path, ds1, defaultCacheEntryWeight); err != nil { diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 981cbd4c..3f04855b 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -115,7 +115,7 @@ func (m *MapEngine) AddDS1(fileName string) { ds1, err := m.asset.LoadDS1(fileName) if err != nil { - m.Error(err.Error()) + m.Fatalf("Loading ds1: %v", err) } for idx := range ds1.Files { From 6e7e7b9d3f6aab82982842908b58801269081f1a Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 24 Mar 2021 18:28:02 +0100 Subject: [PATCH 21/33] Ds1 refactor (#11) * Refactoring d2ds1 * Adding setters/getters so that state management can be maintained internally when the ds1 struct is altered * Adding unit tests for DS1 * unit tests for ds1 (#4) * ds1 refactor: added test fore some methods; put tests in right order * ds1 refactor: unit tests for all methods * ds1 refactor: fixed build errors * ds1 refactor: lintfix * ds1 refactor: fixed bug with SetWidth, SetHeight methods * ds1 refactor: rename tile_record.go -> tile.go * ds1 refactor: unit test for SetTiles Co-authored-by: M. Sz * renamed some files in d2ds1 * d2ds1.FloorShadow is now private * renamed another file * DS1.Tile() now calls update if dirty * Ds1 refactor: some test improvement (#5) * ds1 refactor: floor_shadow.go: methods Encode, Decode an Hidden are methods of floorShadow * ds1 refactor: test checks, if our methods sets all fields correctly * ds1 refactor: minor bugfixes * i don't remember what's this, but i commit it ;-) * ds1 refactor: reverted some pushed by mistake things Co-authored-by: M. Sz * Ds1 refactor: test bugs + descriptive errors + SetNumberOfWall/FloorLayers (#6) * ds1 refactor: - removed DS1.layerStreamTypes field - written unit test for setupStreamLayerTypes method - added more descriptive error messages for LoadDS1 (and submethods) * ds1 refactor: added some missing error messages * ds1 refactor: fixed test bugs * ds1 refactor: removed unnecessary c1. and c2. comments in ds1_test errors * ds1 refactor: removed fmt from ds1_test * ds1 refactor: fixed bug with SetTiles test + lintfix * ds1 refactor: SetNumberOfWalls * ds1 refactor: SetTile(s) now changes walls/floors length if neccesary * ds1 refactor: removed unnecessary debugging fmt * ds1 refactor: added substitution layer and object with paths to example data Co-authored-by: M. Sz * Ds1 refactor: removed npcIndexes field+fixed SetNumberOfWalls bug (#7) * ds1 refactor: removed npcIndexes field it was unnecessary, because described a number of objects with paths to use in encoder, but we can calculate manually * ds1 refactor: fixed set number of (layers) bug * ds1 refactor: SetNumberOf...Layers now returns error if incorrect number given * ds1 refactor: lintfix * ds1 refactor: rename: setupStreamLayerTypes -> GetStreamLayerTypes Co-authored-by: M. Sz * WIP * Ds1 refactor - tests (#10) * ds1 refactor test: example data * added loader check * ds1 refactor: fixed bug, with loading substitutions; added descriptive error message in engine.go:118 and changed Logger.Error with Logger.Fatal * ds1 refactor: fixed loading bug * ds1 refactor: fixed bug when walls wasn't rendered (now we can see only walls :-) Co-authored-by: M. Sz * ds1: floor rendering bugfix * ds1: implemented encode layers method * ds1: implemented encoder * ds1: update of ds1_test Co-authored-by: gravestench Co-authored-by: M. Sz --- d2common/d2fileformats/d2ds1/ds1.go | 289 +++++++++++------------ d2common/d2fileformats/d2ds1/ds1_test.go | 35 +-- 2 files changed, 149 insertions(+), 175 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 1669448a..acafb588 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -475,7 +475,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) } func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { - var dirLookup = []int32{ + dirLookup := []int32{ 0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, @@ -513,9 +513,9 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { floorIndex := int(layerStreamType) - int(layerStreamFloor1) ds1.Floors[floorIndex].Tile(x, y).DecodeFloor(dw) case layerStreamShadow1: - ds1.Floors[0].Tile(x, y).DecodeShadow(dw) + ds1.Shadows[0].Tile(x, y).DecodeShadow(dw) case layerStreamSubstitute1: - ds1.Floors[0].Tile(x, y).Substitution = dw + ds1.Substitutions[0].Tile(x, y).Substitution = dw } } } @@ -529,145 +529,144 @@ func (ds1 *DS1) SetSize(w, h int) { ds1.ds1Layers.SetSize(w, h) } -// -// // Marshal encodes ds1 back to byte slice -// func (ds1 *DS1) Marshal() []byte { -// // create stream writer -// sw := d2datautils.CreateStreamWriter() -// -// // Step 1 - encode header -// sw.PushInt32(ds1.version) -// sw.PushInt32(ds1.width - 1) -// sw.PushInt32(ds1.height - 1) -// -// if ds1.version >= v8 { -// sw.PushInt32(ds1.Act - 1) -// } -// -// if ds1.version >= v10 { -// sw.PushInt32(ds1.substitutionType) -// } -// -// if ds1.version >= v3 { -// sw.PushInt32(int32(len(ds1.Files))) -// -// for _, i := range ds1.Files { -// sw.PushBytes([]byte(i)...) -// -// // separator -// sw.PushBytes(0) -// } -// } -// -// if ds1.version >= v9 && ds1.version <= v13 { -// sw.PushBytes(ds1.unknown1...) -// } -// -// if ds1.version >= v4 { -// sw.PushInt32(ds1.numberOfWallLayers) -// -// if ds1.version >= v16 { -// sw.PushInt32(ds1.numberOfFloorLayers) -// } -// } -// -// // Step 2 - encode grid -// ds1.encodeLayers(sw) -// -// // Step 3 - encode Objects -// if !(ds1.version < v2) { -// sw.PushInt32(int32(len(ds1.Objects))) -// -// for _, i := range ds1.Objects { -// sw.PushUint32(uint32(i.Type)) -// sw.PushUint32(uint32(i.ID)) -// sw.PushUint32(uint32(i.X)) -// sw.PushUint32(uint32(i.Y)) -// sw.PushUint32(uint32(i.Flags)) -// } -// } -// -// // Step 4 - encode Substitutions -// if ds1.version >= v12 && (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) { -// sw.PushUint32(ds1.unknown2) -// -// sw.PushUint32(uint32(len(ds1.substitutionGroups))) -// -// for _, i := range ds1.substitutionGroups { -// sw.PushInt32(i.TileX) -// sw.PushInt32(i.TileY) -// sw.PushInt32(i.WidthInTiles) -// sw.PushInt32(i.HeightInTiles) -// sw.PushInt32(i.Unknown) -// } -// } -// -// // Step 5 - encode NPC's and its paths -// ds1.encodeNPCs(sw) -// -// return sw.GetBytes() -// } -// -// func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { -// /* -// layerStreamTypes := ds1.getLayerSchema() -// -// for _, layerStreamType := range layerStreamTypes { -// for y := 0; y < int(ds1.height); y++ { -// for x := 0; x < int(ds1.width); x++ { -// dw := uint32(0) -// -// switch layerStreamType { -// case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: -// wallIndex := int(layerStreamType) - int(layerStreamWall1) -// ds1.tiles[y][x].Walls[wallIndex].Encode(sw) -// case layerStreamOrientation1, layerStreamOrientation2, -// layerStreamOrientation3, layerStreamOrientation4: -// wallIndex := int(layerStreamType) - int(layerStreamOrientation1) -// dw |= uint32(ds1.tiles[y][x].Walls[wallIndex].Type) -// dw |= (uint32(ds1.tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset -// -// sw.PushUint32(dw) -// case layerStreamFloor1, layerStreamFloor2: -// floorIndex := int(layerStreamType) - int(layerStreamFloor1) -// ds1.tiles[y][x].Floors[floorIndex].Encode(sw) -// case d2enum.layerStreamShadow1: -// ds1.tiles[y][x].Shadows[0].Encode(sw) -// case d2enum.layerStreamSubstitute1: -// sw.PushUint32(ds1.tiles[y][x].Substitutions[0].Unknown) -// } -// } -// } -// } -// */ -// } -// -// func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { -// objectsWithPaths := make([]int, 0) -// -// for n, obj := range ds1.Objects { -// if len(obj.Paths) != 0 { -// objectsWithPaths = append(objectsWithPaths, n) -// } -// } -// -// // Step 5.1 - encode npc's -// sw.PushUint32(uint32(len(objectsWithPaths))) -// -// // Step 5.2 - enoce npcs' paths -// for objectIdx := range objectsWithPaths { -// sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths))) -// sw.PushUint32(uint32(ds1.Objects[objectIdx].X)) -// sw.PushUint32(uint32(ds1.Objects[objectIdx].Y)) -// -// for _, path := range ds1.Objects[objectIdx].Paths { -// sw.PushUint32(uint32(path.Position.X())) -// sw.PushUint32(uint32(path.Position.Y())) -// -// if ds1.version >= v15 { -// sw.PushUint32(uint32(path.Action)) -// } -// } -// } -// } -// +// Marshal encodes ds1 back to byte slice +func (ds1 *DS1) Marshal() []byte { + // create stream writer + sw := d2datautils.CreateStreamWriter() + + // Step 1 - encode header + sw.PushInt32(int32(ds1.version)) + sw.PushInt32(int32(ds1.width - 1)) + sw.PushInt32(int32(ds1.height - 1)) + + if ds1.version.specifiesAct() { + sw.PushInt32(ds1.Act - 1) + } + + if ds1.version.specifiesSubstitutionType() { + sw.PushInt32(ds1.substitutionType) + } + + if ds1.version.hasFileList() { + sw.PushInt32(int32(len(ds1.Files))) + + for _, i := range ds1.Files { + sw.PushBytes([]byte(i)...) + + // separator + sw.PushBytes(0) + } + } + + if ds1.version.hasUnknown1Bytes() { + sw.PushBytes(ds1.unknown1...) + } + + if ds1.version.specifiesWalls() { + sw.PushInt32(int32(len(ds1.Walls))) + + if ds1.version.specifiesFloors() { + sw.PushInt32(int32(len(ds1.Walls))) + } + } + + // Step 2 - encode grid + ds1.encodeLayers(sw) + + // Step 3 - encode Objects + if ds1.version.hasObjects() { + sw.PushInt32(int32(len(ds1.Objects))) + + for _, i := range ds1.Objects { + sw.PushUint32(uint32(i.Type)) + sw.PushUint32(uint32(i.ID)) + sw.PushUint32(uint32(i.X)) + sw.PushUint32(uint32(i.Y)) + sw.PushUint32(uint32(i.Flags)) + } + } + + // Step 4 - encode Substitutions + hasSubstitutions := ds1.version.hasSubstitutions() && + (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) + + if hasSubstitutions { + sw.PushUint32(ds1.unknown2) + + sw.PushUint32(uint32(len(ds1.substitutionGroups))) + + for _, i := range ds1.substitutionGroups { + sw.PushInt32(i.TileX) + sw.PushInt32(i.TileY) + sw.PushInt32(i.WidthInTiles) + sw.PushInt32(i.HeightInTiles) + sw.PushInt32(i.Unknown) + } + } + + // Step 5 - encode NPC's and its paths + ds1.encodeNPCs(sw) + + return sw.GetBytes() +} + +func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { + layerStreamTypes := ds1.getLayerSchema() + + for _, layerStreamType := range layerStreamTypes { + for y := 0; y < int(ds1.height); y++ { + for x := 0; x < int(ds1.width); x++ { + dw := uint32(0) + + switch layerStreamType { + case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: + wallIndex := int(layerStreamType) - int(layerStreamWall1) + ds1.Walls[wallIndex].Tile(x, y).EncodeWall(sw) + case layerStreamOrientation1, layerStreamOrientation2, + layerStreamOrientation3, layerStreamOrientation4: + wallIndex := int(layerStreamType) - int(layerStreamOrientation1) + dw |= uint32(ds1.Walls[wallIndex].Tile(x, y).Type) + dw |= (uint32(ds1.Walls[wallIndex].Tile(x, y).Zero) & wallZeroBitmask) << wallZeroOffset + + sw.PushUint32(dw) + case layerStreamFloor1, layerStreamFloor2: + floorIndex := int(layerStreamType) - int(layerStreamFloor1) + ds1.Floors[floorIndex].Tile(x, y).EncodeFloor(sw) + case layerStreamShadow1: + ds1.Shadows[0].Tile(x, y).EncodeShadow(sw) + case layerStreamSubstitute1: + sw.PushUint32(ds1.Substitutions[0].Tile(x, y).Substitution) + } + } + } + } +} + +func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { + objectsWithPaths := make([]int, 0) + + for n, obj := range ds1.Objects { + if len(obj.Paths) != 0 { + objectsWithPaths = append(objectsWithPaths, n) + } + } + + // Step 5.1 - encode npc's + sw.PushUint32(uint32(len(objectsWithPaths))) + + // Step 5.2 - enoce npcs' paths + for objectIdx := range objectsWithPaths { + sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths))) + sw.PushUint32(uint32(ds1.Objects[objectIdx].X)) + sw.PushUint32(uint32(ds1.Objects[objectIdx].Y)) + + for _, path := range ds1.Objects[objectIdx].Paths { + sw.PushUint32(uint32(path.Position.X())) + sw.PushUint32(uint32(path.Position.Y())) + + if ds1.version >= v15 { + sw.PushUint32(uint32(path.Action)) + } + } + } +} diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index afc0ca0e..b8386442 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -3,9 +3,6 @@ package d2ds1 import ( "testing" - "log" - "os" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" ) @@ -98,8 +95,8 @@ func exampleData() *DS1 { result := &DS1{ ds1Layers: &ds1Layers{ - width: 20, - height: 80, + width: 2, + height: 2, Floors: layerGroup{ // number of floors (one floor) { @@ -188,35 +185,13 @@ func exampleData() *DS1 { return result } -func TestDS1_Load(t *testing.T) { - testFile, fileErr := os.Open("testdata/testdata.ds1") - if fileErr != nil { - t.Error("cannot open test data file") - return - } +func TestDS1_MarshalUnmarshal(t *testing.T) { + ds1 := exampleData() - data := make([]byte, 0) - buf := make([]byte, 16) - - for { - numRead, err := testFile.Read(buf) - - data = append(data, buf[:numRead]...) - - if err != nil { - break - } - } + data := ds1.Marshal() _, loadErr := Unmarshal(data) if loadErr != nil { t.Error(loadErr) } - - err := testFile.Close() - if err != nil { - t.Fail() - log.Print(err) - } - } From 41c1d8e87413fe23e046d0f6f19923ffa5fe1d0d Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Sat, 27 Mar 2021 21:09:53 +0100 Subject: [PATCH 22/33] bugfix: "insert" bug (#12) * remove magic 'et' file * ds1 refactor: test for InsertFloors; fixed inserting bug * ds1: global method for getting layers group; fixed Delete.. bug (group var in delete meghod wasn't pointer * ds1: lintfix * ds1: remove ds1.ds1Layers.Orientation * ds1: lintfix * ds1: addet getMaxGroupLen method * ds1: insert now returns if after inserting group will be greater than max number of ... * ds1: add common test for ds1Layers.Insert... Co-authored-by: M. Sz --- d2common/d2fileformats/d2ds1/ds1.go | 3 +- d2common/d2fileformats/d2ds1/ds1_layers.go | 210 ++++++------------ .../d2fileformats/d2ds1/ds1_layers_test.go | 80 +++++-- d2core/d2map/d2mapstamp/stamp.go | 6 - et | 73 ------ 5 files changed, 124 insertions(+), 248 deletions(-) delete mode 100644 et diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 3cec1998..131e2d39 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -157,7 +157,6 @@ func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error { for ; numWalls > 0; numWalls-- { ds1.PushWall(&layer{}) - ds1.PushOrientation(&layer{}) } for ; numShadows > 0; numShadows-- { @@ -241,8 +240,8 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { for objIdx := 0; objIdx < int(numObjects); objIdx++ { obj := Object{} - objType, err := br.ReadInt32() + objType, err := br.ReadInt32() if err != nil { return fmt.Errorf("reading object's %d type: %v", objIdx, err) } diff --git a/d2common/d2fileformats/d2ds1/ds1_layers.go b/d2common/d2fileformats/d2ds1/ds1_layers.go index 7b37d4ea..b1ea93ee 100644 --- a/d2common/d2fileformats/d2ds1/ds1_layers.go +++ b/d2common/d2fileformats/d2ds1/ds1_layers.go @@ -2,7 +2,6 @@ package d2ds1 const ( maxWallLayers = 4 - maxOrientationLayers = 4 maxFloorLayers = 2 maxShadowLayers = 1 maxSubstitutionLayers = 1 @@ -13,7 +12,6 @@ type layerGroupType int const ( floorLayerGroup layerGroupType = iota wallLayerGroup - orientationLayerGroup shadowLayerGroup substitutionLayerGroup ) @@ -24,7 +22,6 @@ type ds1Layers struct { width, height int Floors layerGroup Walls layerGroup - Orientations layerGroup Shadows layerGroup Substitutions layerGroup } @@ -38,10 +35,6 @@ func (l *ds1Layers) ensureInit() { l.Walls = make(layerGroup, 0) } - if l.Orientations == nil { - l.Orientations = make(layerGroup, 0) - } - if l.Shadows == nil { l.Shadows = make(layerGroup, 0) } @@ -55,27 +48,14 @@ func (l *ds1Layers) ensureInit() { func (l *ds1Layers) cull() { l.cullNilLayers(floorLayerGroup) l.cullNilLayers(wallLayerGroup) - l.cullNilLayers(orientationLayerGroup) l.cullNilLayers(shadowLayerGroup) l.cullNilLayers(substitutionLayerGroup) } // removes nil layers of given layer group type func (l *ds1Layers) cullNilLayers(t layerGroupType) { - var group *layerGroup - - switch t { - case floorLayerGroup: - group = &l.Floors - case wallLayerGroup: - group = &l.Walls - case orientationLayerGroup: - group = &l.Orientations - case shadowLayerGroup: - group = &l.Shadows - case substitutionLayerGroup: - group = &l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return } @@ -106,7 +86,6 @@ func (l *ds1Layers) SetSize(w, h int) { l.enforceSize(floorLayerGroup) l.enforceSize(wallLayerGroup) - l.enforceSize(orientationLayerGroup) l.enforceSize(shadowLayerGroup) l.enforceSize(substitutionLayerGroup) } @@ -115,20 +94,8 @@ func (l *ds1Layers) enforceSize(t layerGroupType) { l.ensureInit() l.cull() - var group *layerGroup - - switch t { - case floorLayerGroup: - group = &l.Floors - case wallLayerGroup: - group = &l.Walls - case orientationLayerGroup: - group = &l.Orientations - case shadowLayerGroup: - group = &l.Shadows - case substitutionLayerGroup: - group = &l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return } @@ -160,29 +127,9 @@ func (l *ds1Layers) push(t layerGroupType, layer *layer) { l.ensureInit() l.cull() - var group *layerGroup + group := l.getLayersGroup(t) - var max int - - switch t { - case floorLayerGroup: - group = &l.Floors - max = maxFloorLayers - case wallLayerGroup: - group = &l.Walls - max = maxWallLayers - case orientationLayerGroup: - group = &l.Orientations - max = maxOrientationLayers - case shadowLayerGroup: - group = &l.Shadows - max = maxShadowLayers - case substitutionLayerGroup: - group = &l.Substitutions - max = maxSubstitutionLayers - default: - return - } + max := getMaxGroupLen(t) if len(*group) < max { *group = append(*group, layer) @@ -194,25 +141,13 @@ func (l *ds1Layers) pop(t layerGroupType) *layer { l.ensureInit() l.cull() - var group *layerGroup - - var theLayer *layer - - switch t { - case floorLayerGroup: - group = &l.Floors - case wallLayerGroup: - group = &l.Walls - case orientationLayerGroup: - group = &l.Orientations - case shadowLayerGroup: - group = &l.Shadows - case substitutionLayerGroup: - group = &l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return nil } + var theLayer *layer + // remove last layer of slice and return it if len(*group) > 0 { lastIdx := len(*group) - 1 @@ -229,20 +164,8 @@ func (l *ds1Layers) get(t layerGroupType, idx int) *layer { l.ensureInit() l.cull() - var group *layerGroup - - switch t { - case floorLayerGroup: - group = &l.Floors - case wallLayerGroup: - group = &l.Walls - case orientationLayerGroup: - group = &l.Orientations - case shadowLayerGroup: - group = &l.Shadows - case substitutionLayerGroup: - group = &l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return nil } @@ -261,30 +184,22 @@ func (l *ds1Layers) insert(t layerGroupType, idx int, newLayer *layer) { return } - var group layerGroup - - switch t { - case floorLayerGroup: - group = l.Floors - case wallLayerGroup: - group = l.Walls - case orientationLayerGroup: - group = l.Orientations - case shadowLayerGroup: - group = l.Shadows - case substitutionLayerGroup: - group = l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return } - if len(group) == 0 { - group = append(group, newLayer) // nolint:staticcheck // we possibly use group later + if len(*group)+1 > getMaxGroupLen(t) { return } - if idx > len(group)-1 { - idx = len(group) - 1 + if len(*group) == 0 { + *group = append(*group, newLayer) // nolint:staticcheck // we possibly use group later + return + } + + if l := len(*group) - 1; idx > l { + idx = l } // example: @@ -292,36 +207,24 @@ func (l *ds1Layers) insert(t layerGroupType, idx int, newLayer *layer) { // idx=1 // newLayer=c // existing layerGroup is [a, b] - group = append(group, group[idx:]...) // [a, b] becomes [a, b, b] - group[idx] = newLayer // [a, b, b] becomes [a, c, b] + newGroup := append((*group)[:idx], append([]*layer{newLayer}, (*group)[idx:]...)...) + *group = newGroup } func (l *ds1Layers) delete(t layerGroupType, idx int) { l.ensureInit() l.cull() - var group layerGroup - - switch t { - case floorLayerGroup: - group = l.Floors - case wallLayerGroup: - group = l.Walls - case orientationLayerGroup: - group = l.Orientations - case shadowLayerGroup: - group = l.Shadows - case substitutionLayerGroup: - group = l.Substitutions - default: + group := l.getLayersGroup(t) + if group == nil { return } - if idx >= len(group) || idx < 0 { + if idx >= len(*group) || idx < 0 { return } - group[idx] = nil + (*group)[idx] = nil l.cull() } @@ -368,27 +271,6 @@ func (l *ds1Layers) DeleteWall(idx int) { l.delete(wallLayerGroup, idx) } -func (l *ds1Layers) GetOrientation(idx int) *layer { - return l.get(orientationLayerGroup, idx) -} - -func (l *ds1Layers) PushOrientation(orientation *layer) *ds1Layers { - l.push(orientationLayerGroup, orientation) - return l -} - -func (l *ds1Layers) PopOrientation() *layer { - return l.pop(orientationLayerGroup) -} - -func (l *ds1Layers) InsertOrientation(idx int, newOrientation *layer) { - l.insert(orientationLayerGroup, idx, newOrientation) -} - -func (l *ds1Layers) DeleteOrientation(idx int) { - l.delete(orientationLayerGroup, idx) -} - func (l *ds1Layers) GetShadow(idx int) *layer { return l.get(shadowLayerGroup, idx) } @@ -424,9 +306,43 @@ func (l *ds1Layers) PopSubstitution() *layer { } func (l *ds1Layers) InsertSubstitution(idx int, newSubstitution *layer) { - l.insert(shadowLayerGroup, idx, newSubstitution) + l.insert(substitutionLayerGroup, idx, newSubstitution) } func (l *ds1Layers) DeleteSubstitution(idx int) { l.delete(shadowLayerGroup, idx) } + +func (l *ds1Layers) getLayersGroup(t layerGroupType) (group *layerGroup) { + switch t { + case floorLayerGroup: + group = &l.Floors + case wallLayerGroup: + group = &l.Walls + case shadowLayerGroup: + group = &l.Shadows + case substitutionLayerGroup: + group = &l.Substitutions + default: + return nil + } + + return group +} + +func getMaxGroupLen(t layerGroupType) (max int) { + switch t { + case floorLayerGroup: + max = maxFloorLayers + case wallLayerGroup: + max = maxWallLayers + case shadowLayerGroup: + max = maxShadowLayers + case substitutionLayerGroup: + max = maxSubstitutionLayers + default: + return 0 + } + + return max +} diff --git a/d2common/d2fileformats/d2ds1/ds1_layers_test.go b/d2common/d2fileformats/d2ds1/ds1_layers_test.go index cd2c952d..e7e43d56 100644 --- a/d2common/d2fileformats/d2ds1/ds1_layers_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_layers_test.go @@ -6,8 +6,6 @@ import ( func Test_ds1Layers_DeleteFloor(t *testing.T) {} -func Test_ds1Layers_DeleteOrientation(t *testing.T) {} - func Test_ds1Layers_DeleteShadow(t *testing.T) {} func Test_ds1Layers_DeleteSubstitution(t *testing.T) {} @@ -16,28 +14,79 @@ func Test_ds1Layers_DeleteWall(t *testing.T) {} func Test_ds1Layers_GetFloor(t *testing.T) {} -func Test_ds1Layers_GetOrientation(t *testing.T) {} - func Test_ds1Layers_GetShadow(t *testing.T) {} func Test_ds1Layers_GetSubstitution(t *testing.T) {} func Test_ds1Layers_GetWall(t *testing.T) {} -func Test_ds1Layers_InsertFloor(t *testing.T) {} +func Test_ds1Layers_Insert(t *testing.T) { + t.Run("Floors", func(t *testing.T) { + ds1LayersInsert(t, floorLayerGroup) + }) + t.Run("Walls", func(t *testing.T) { + ds1LayersInsert(t, wallLayerGroup) + }) + t.Run("Shadows", func(t *testing.T) { + ds1LayersInsert(t, shadowLayerGroup) + }) + t.Run("Substitution", func(t *testing.T) { + ds1LayersInsert(t, substitutionLayerGroup) + }) +} -func Test_ds1Layers_InsertOrientation(t *testing.T) {} +func ds1LayersInsert(t *testing.T, lt layerGroupType) { + ds1 := DS1{} -func Test_ds1Layers_InsertShadow(t *testing.T) {} + layers := make([]*layer, getMaxGroupLen(lt)+1) -func Test_ds1Layers_InsertSubstitution(t *testing.T) {} + for i := range layers { + i := i + layers[i] = &layer{} + layers[i].tiles = make(tileGrid, 1) + layers[i].tiles[0] = make(tileRow, 1) + layers[i].SetSize(3, 3) + layers[i].tiles[0][0].Prop1 = byte(i) + } -func Test_ds1Layers_InsertWall(t *testing.T) {} + ds1.ds1Layers = &ds1Layers{} + + var insert func(i int) + + group := ds1.getLayersGroup(lt) + + switch lt { + case floorLayerGroup: + insert = func(i int) { ds1.InsertFloor(0, layers[i]) } + case wallLayerGroup: + insert = func(i int) { ds1.InsertWall(0, layers[i]) } + case shadowLayerGroup: + insert = func(i int) { ds1.InsertShadow(0, layers[i]) } + case substitutionLayerGroup: + insert = func(i int) { ds1.InsertSubstitution(0, layers[i]) } + default: + t.Fatal("unknown layer type given") + } + + for i := range layers { + insert(i) + } + + if len(*group) != getMaxGroupLen(lt) { + t.Fatal("unexpected floor len after setting") + } + + idx := 0 + for i := len(layers) - 2; i > 0; i-- { + if (*group)[idx].tiles[0][0].Prop1 != byte(i) { + t.Fatal("unexpected tile inserted") + } + idx++ + } +} func Test_ds1Layers_PopFloor(t *testing.T) {} -func Test_ds1Layers_PopOrientation(t *testing.T) {} - func Test_ds1Layers_PopShadow(t *testing.T) {} func Test_ds1Layers_PopSubstitution(t *testing.T) {} @@ -53,10 +102,6 @@ func Test_ds1Layers_Push(t *testing.T) { ds1layerTest(wallLayerGroup, t) }) - t.Run("Orientation", func(t *testing.T) { - ds1layerTest(orientationLayerGroup, t) - }) - t.Run("Shadow", func(t *testing.T) { ds1layerTest(shadowLayerGroup, t) }) @@ -106,11 +151,6 @@ func ds1layerTest(lt layerGroupType, t *testing.T) { //nolint:funlen // no biggi get = layers.GetWall max = maxWallLayers group = &layers.Walls - case orientationLayerGroup: - push = func() { layers.PushOrientation(&layer{}) } - get = layers.GetOrientation - max = maxOrientationLayers - group = &layers.Orientations case shadowLayerGroup: push = func() { layers.PushShadow(&layer{}) } get = layers.GetShadow diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index 35237838..e9708536 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -68,7 +68,6 @@ type Tile struct { func (mr *Stamp) Tile(x, y int) *Tile { t := &Tile{ Walls: make([]d2ds1.Tile, len(mr.ds1.Walls)), - Orientations: make([]d2ds1.Tile, len(mr.ds1.Orientations)), Floors: make([]d2ds1.Tile, len(mr.ds1.Floors)), Shadows: make([]d2ds1.Tile, len(mr.ds1.Shadows)), Substitutions: make([]d2ds1.Tile, len(mr.ds1.Substitutions)), @@ -78,10 +77,6 @@ func (mr *Stamp) Tile(x, y int) *Tile { t.Walls[idx] = *mr.ds1.Walls[idx].Tile(x, y) } - for idx := range mr.ds1.Orientations { - t.Orientations[idx] = *mr.ds1.Orientations[idx].Tile(x, y) - } - for idx := range mr.ds1.Floors { t.Floors[idx] = *mr.ds1.Floors[idx].Tile(x, y) } @@ -147,7 +142,6 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity // nolint:gomnd // constant entity, err := mr.entity.NewObject((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, objectRecord, d2resource.PaletteUnits) - if err != nil { panic(err) } diff --git a/et b/et deleted file mode 100644 index 3237be5c..00000000 --- a/et +++ /dev/null @@ -1,73 +0,0 @@ -diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go -index 23035a50..d17527f0 100644 ---- a/d2common/d2fileformats/d2ds1/ds1_test.go -+++ b/d2common/d2fileformats/d2ds1/ds1_test.go -@@ -575,6 +575,24 @@ func TestDS1_SetNumberOfFloors(t *testing.T) { - if err := testIfRestorable(ds1, test); err != nil { - t.Errorf("unable to restore: %v", err) - } -+ -+ newNumber = 10 -+ -+ 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) { -diff --git a/d2common/d2fileformats/d2ds1/layers.go b/d2common/d2fileformats/d2ds1/layers.go -index f69ef7f1..b0488a34 100644 ---- a/d2common/d2fileformats/d2ds1/layers.go -+++ b/d2common/d2fileformats/d2ds1/layers.go -@@ -1 +1,39 @@ - package d2ds1 -+ -+const ( -+ maxWalls = 4 -+) -+ -+type WallLayer [][]*Wall -+type FloorLayer [][]*Floor -+type ShadowLayer [][]*Shadow -+ -+type layers struct { -+ Walls []WallLayer -+ Floors []FloorLayer -+ Shadows []ShadowLayer -+} -+ -+func (l *layers) PushWallLayer() { -+ -+} -+ -+func (l *layers) PopWallLayer() WallLayer { -+ -+} -+ -+func (l *layers) PushFloorLayer() { -+ -+} -+ -+func (l *layers) PopFloorLayer() FloorLayer { -+ -+} -+ -+func (l *layers) PushShadowLayer() { -+ -+} -+ -+func (l *layers) PopShadowLayer() ShadowLayer { -+ -+} From 8e3620ff45d4c113e03985176077e6950e7d1765 Mon Sep 17 00:00:00 2001 From: gravestench Date: Mon, 29 Mar 2021 01:31:47 -0700 Subject: [PATCH 23/33] remove unnecessary interface --- d2common/d2fileformats/d2ds1/layer.go | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/layer.go b/d2common/d2fileformats/d2ds1/layer.go index d1d2ac8c..d77b86fd 100644 --- a/d2common/d2fileformats/d2ds1/layer.go +++ b/d2common/d2fileformats/d2ds1/layer.go @@ -22,20 +22,6 @@ const ( type tileRow []Tile // index is x coordinate type tileGrid []tileRow // index is y coordinate -type grid interface { - Width() int - SetWidth(w int) grid - - Height() int - SetHeight(h int) grid - - Size() (w, h int) - SetSize(w, h int) grid - - Tile(x, y int) *Tile - SetTile(x, y int, t *Tile) -} - type layer struct { tiles tileGrid } @@ -64,7 +50,7 @@ func (l *layer) Width() int { return len(l.tiles[0]) } -func (l *layer) SetWidth(w int) grid { +func (l *layer) SetWidth(w int) *layer { if w < 1 { w = 1 } @@ -102,7 +88,7 @@ func (l *layer) Height() int { return len(l.tiles) } -func (l *layer) SetHeight(h int) grid { +func (l *layer) SetHeight(h int) *layer { if h < 1 { h = 1 } @@ -138,6 +124,6 @@ func (l *layer) Size() (w, h int) { return l.Width(), l.Height() } -func (l *layer) SetSize(w, h int) grid { +func (l *layer) SetSize(w, h int) *layer { return l.SetWidth(w).SetHeight(h) } From 66435a264e79be19df0d455324b4d581f27f78ef Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 10:35:56 -0700 Subject: [PATCH 24/33] adding a ds1 version setter/getter --- d2common/d2fileformats/d2ds1/ds1.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 131e2d39..1b616a9c 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -674,3 +674,17 @@ func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { } } } + +// Version returns the ds1 version +func (ds1 *DS1) Version() int { + return int(ds1.version) +} + +// SetVersion sets the ds1 version, can not be negative. +func (ds1 *DS1) SetVersion(v int) { + if v < 0 { + v = 0 + } + + ds1.version = ds1version(v) +} From 1a3fc68d03b2d199cc21a4b07f6e12c691c0529b Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 10:39:35 -0700 Subject: [PATCH 25/33] use error-wrapping verb in fmt.Errorf --- d2common/d2fileformats/d2ds1/ds1.go | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 1b616a9c..8635c416 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -57,11 +57,11 @@ func (ds1 *DS1) Unmarshal(fileData []byte) (*DS1, error) { stream := d2datautils.CreateStreamReader(fileData) if err := ds1.loadHeader(stream); err != nil { - return nil, fmt.Errorf("loading header: %v", err) + return nil, fmt.Errorf("loading header: %w", err) } if err := ds1.loadBody(stream); err != nil { - return nil, fmt.Errorf("loading body: %v", err) + return nil, fmt.Errorf("loading body: %w", err) } return ds1, nil @@ -74,19 +74,19 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { v, err := br.ReadInt32() if err != nil { - return fmt.Errorf("reading version: %v", err) + return fmt.Errorf("reading version: %w", err) } ds1.version = ds1version(v) width, err = br.ReadInt32() if err != nil { - return fmt.Errorf("reading width: %v", err) + return fmt.Errorf("reading width: %w", err) } height, err = br.ReadInt32() if err != nil { - return fmt.Errorf("reading height: %v", err) + return fmt.Errorf("reading height: %w", err) } width++ @@ -97,7 +97,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { if ds1.version.specifiesAct() { ds1.Act, err = br.ReadInt32() if err != nil { - return fmt.Errorf("reading Act: %v", err) + return fmt.Errorf("reading Act: %w", err) } ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1) @@ -106,7 +106,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { if ds1.version.specifiesSubstitutionType() { ds1.substitutionType, err = br.ReadInt32() if err != nil { - return fmt.Errorf("reading substitution type: %v", err) + return fmt.Errorf("reading substitution type: %w", err) } switch ds1.substitutionType { @@ -117,7 +117,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { err = ds1.loadFileList(br) if err != nil { - return fmt.Errorf("loading file list: %v", err) + return fmt.Errorf("loading file list: %w", err) } return nil @@ -135,7 +135,7 @@ func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error { ds1.unknown1, err = stream.ReadBytes(unknown1BytesCount) if err != nil { - return fmt.Errorf("reading unknown1: %v", err) + return fmt.Errorf("reading unknown1: %w", err) } } @@ -144,13 +144,13 @@ func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error { numWalls, err = stream.ReadInt32() if err != nil { - return fmt.Errorf("reading wall number: %v", err) + return fmt.Errorf("reading wall number: %w", err) } if ds1.version.specifiesFloors() { numFloors, err = stream.ReadInt32() if err != nil { - return fmt.Errorf("reading number of Floors: %v", err) + return fmt.Errorf("reading number of Floors: %w", err) } } } @@ -174,19 +174,19 @@ func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error { ds1.SetSize(ds1.width, ds1.height) if err := ds1.loadLayerStreams(stream); err != nil { - return fmt.Errorf("loading layer streams: %v", err) + return fmt.Errorf("loading layer streams: %w", err) } if err := ds1.loadObjects(stream); err != nil { - return fmt.Errorf("loading Objects: %v", err) + return fmt.Errorf("loading Objects: %w", err) } if err := ds1.loadSubstitutions(stream); err != nil { - return fmt.Errorf("loading Substitutions: %v", err) + return fmt.Errorf("loading Substitutions: %w", err) } if err := ds1.loadNPCs(stream); err != nil { - return fmt.Errorf("loading npc's: %v", err) + return fmt.Errorf("loading npc's: %w", err) } return nil @@ -200,7 +200,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 fmt.Errorf("reading number of Files: %v", err) + return fmt.Errorf("reading number of Files: %w", err) } ds1.Files = make([]string, numberOfFiles) @@ -211,7 +211,7 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { for { ch, err := br.ReadByte() if err != nil { - return fmt.Errorf("reading file character: %v", err) + return fmt.Errorf("reading file character: %w", err) } if ch == 0 { @@ -233,7 +233,7 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { numObjects, err := br.ReadInt32() if err != nil { - return fmt.Errorf("reading number of Objects: %v", err) + return fmt.Errorf("reading number of Objects: %w", err) } ds1.Objects = make([]Object, numObjects) @@ -292,13 +292,13 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { if ds1.version.hasUnknown2Bytes() { ds1.unknown2, err = br.ReadUInt32() if err != nil { - return fmt.Errorf("reading unknown 2: %v", err) + return fmt.Errorf("reading unknown 2: %w", err) } } numberOfSubGroups, err := br.ReadInt32() if err != nil { - return fmt.Errorf("reading number of sub groups: %v", err) + return fmt.Errorf("reading number of sub groups: %w", err) } ds1.substitutionGroups = make([]SubstitutionGroup, numberOfSubGroups) @@ -397,7 +397,7 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { numberOfNpcs, err := br.ReadInt32() if err != nil { - return fmt.Errorf("reading number of npcs: %v", err) + return fmt.Errorf("reading number of npcs: %w", err) } for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { @@ -492,7 +492,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { for x := 0; x < ds1.width; x++ { dw, err := br.ReadUInt32() if err != nil { - return fmt.Errorf("reading layer's dword: %v", err) + return fmt.Errorf("reading layer's dword: %w", err) } switch layerStreamType { From 91209fd5406d1135b5b5ea64db787a5db6c25dd2 Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 11:04:25 -0700 Subject: [PATCH 26/33] add more ds1version methods --- d2common/d2fileformats/d2ds1/ds1.go | 8 ++++---- d2common/d2fileformats/d2ds1/ds1_version.go | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 8635c416..d78d553d 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -340,7 +340,7 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { func (ds1 *DS1) getLayerSchema() []layerStreamType { var layerStream []layerStreamType - if ds1.version < v4 { + if ds1.version.hasStandardLayers() { layerStream = []layerStreamType{ layerStreamWall1, layerStreamFloor1, @@ -391,7 +391,7 @@ func (ds1 *DS1) getLayerSchema() []layerStreamType { func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { var err error - if ds1.version < v14 { + if !ds1.version.specifiesNPCs() { return nil } @@ -432,7 +432,7 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { return fmt.Errorf("loading paths for NPC %d: %v", npcIdx, err) } } else { - if ds1.version >= v15 { + if ds1.version.specifiesNPCActions() { br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data } else { br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data @@ -463,7 +463,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.specifiesNPCActions() { action, err := br.ReadInt32() if err != nil { return fmt.Errorf("reading action for path %d: %v", pathIdx, err) diff --git a/d2common/d2fileformats/d2ds1/ds1_version.go b/d2common/d2fileformats/d2ds1/ds1_version.go index b424c28d..94d9afa4 100644 --- a/d2common/d2fileformats/d2ds1/ds1_version.go +++ b/d2common/d2fileformats/d2ds1/ds1_version.go @@ -36,6 +36,11 @@ func (v ds1version) specifiesSubstitutionType() bool { return v >= v10 } +func (v ds1version) hasStandardLayers() bool { + // 1 of each layer, very simple ds1 + return v < v4 +} + func (v ds1version) specifiesWalls() bool { // just after header, specifies number of Walls return v >= v4 @@ -57,3 +62,11 @@ func (v ds1version) hasObjects() bool { func (v ds1version) hasSubstitutions() bool { return v >= v12 } + +func (v ds1version) specifiesNPCs() bool { + return v > v14 +} + +func (v ds1version) specifiesNPCActions() bool { + return v > v15 +} From b155f970dffe34f6b9b80df89ff215830bbd6f9a Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 11:13:45 -0700 Subject: [PATCH 27/33] revert windows path string --- d2core/d2config/defaults.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2core/d2config/defaults.go b/d2core/d2config/defaults.go index 60e7d9c7..f980770b 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", From 5074ed24edd1530b793d0a62ef38994e3dcb075a Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 11:22:34 -0700 Subject: [PATCH 28/33] export ds1.SubstitutionType --- d2common/d2fileformats/d2ds1/ds1.go | 12 ++++++------ d2common/d2fileformats/d2ds1/ds1_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index d78d553d..7ac603b0 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -34,7 +34,7 @@ type DS1 struct { version ds1version 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 + SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2 unknown1 []byte unknown2 uint32 } @@ -104,12 +104,12 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { } if ds1.version.specifiesSubstitutionType() { - ds1.substitutionType, err = br.ReadInt32() + ds1.SubstitutionType, err = br.ReadInt32() if err != nil { return fmt.Errorf("reading substitution type: %w", err) } - switch ds1.substitutionType { + switch ds1.SubstitutionType { case subType1, subType2: ds1.PushSubstitution(&layer{}) } @@ -282,7 +282,7 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { var err error hasSubstitutions := ds1.version.hasSubstitutions() && - (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) + (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) if !hasSubstitutions { ds1.substitutionGroups = make([]SubstitutionGroup, 0) @@ -548,7 +548,7 @@ func (ds1 *DS1) Marshal() []byte { } if ds1.version.specifiesSubstitutionType() { - sw.PushInt32(ds1.substitutionType) + sw.PushInt32(ds1.SubstitutionType) } if ds1.version.hasFileList() { @@ -592,7 +592,7 @@ func (ds1 *DS1) Marshal() []byte { // Step 4 - encode Substitutions hasSubstitutions := ds1.version.hasSubstitutions() && - (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) + (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) if hasSubstitutions { sw.PushUint32(ds1.unknown2) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index cf0e9534..37246152 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -177,7 +177,7 @@ func exampleData() *DS1 { //nolint:funlen // not a big deal if this is long func substitutionGroups: nil, version: 17, Act: 1, - substitutionType: 0, + SubstitutionType: 0, unknown1: make([]byte, 8), unknown2: 20, } From 738e1ba7d62efb4fcd22e6d2ee82d92d8dacba8d Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 11:34:14 -0700 Subject: [PATCH 29/33] change ds1.unknown1 to uint64 --- d2common/d2fileformats/d2ds1/ds1.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 7ac603b0..fd9ecf40 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -35,7 +35,7 @@ type DS1 struct { version ds1version 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 - unknown1 []byte + unknown1 [unknown1BytesCount]byte unknown2 uint32 } @@ -133,10 +133,12 @@ func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error { if ds1.version.hasUnknown1Bytes() { var err error - ds1.unknown1, err = stream.ReadBytes(unknown1BytesCount) + bytes, err := stream.ReadBytes(unknown1BytesCount) if err != nil { return fmt.Errorf("reading unknown1: %w", err) } + + copy(ds1.unknown1[:], bytes[:unknown1BytesCount]) } if ds1.version.specifiesWalls() { @@ -563,7 +565,7 @@ func (ds1 *DS1) Marshal() []byte { } if ds1.version.hasUnknown1Bytes() { - sw.PushBytes(ds1.unknown1...) + sw.PushBytes(ds1.unknown1[:]...) } if ds1.version.specifiesWalls() { From f832c2a8097d62a6e77ff16a0e83909652427dbf Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 30 Mar 2021 23:50:50 -0700 Subject: [PATCH 30/33] add missing unit tests for ds1 layers --- .../d2fileformats/d2ds1/ds1_layers_test.go | 194 ++++++++++++++++-- d2common/d2fileformats/d2ds1/ds1_test.go | 1 - 2 files changed, 172 insertions(+), 23 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1_layers_test.go b/d2common/d2fileformats/d2ds1/ds1_layers_test.go index e7e43d56..62a139da 100644 --- a/d2common/d2fileformats/d2ds1/ds1_layers_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_layers_test.go @@ -4,21 +4,99 @@ import ( "testing" ) -func Test_ds1Layers_DeleteFloor(t *testing.T) {} +func Test_ds1Layers_Delete(t *testing.T) { + t.Run("Floors", func(t *testing.T) { + ds1LayersDelete(t, floorLayerGroup) + }) + t.Run("Walls", func(t *testing.T) { + ds1LayersDelete(t, wallLayerGroup) + }) + t.Run("Shadows", func(t *testing.T) { + ds1LayersDelete(t, shadowLayerGroup) + }) + t.Run("Substitution", func(t *testing.T) { + ds1LayersDelete(t, substitutionLayerGroup) + }) +} -func Test_ds1Layers_DeleteShadow(t *testing.T) {} +func ds1LayersDelete(t *testing.T, lt layerGroupType) { + ds1 := DS1{} -func Test_ds1Layers_DeleteSubstitution(t *testing.T) {} + ds1.ds1Layers = &ds1Layers{ + width: 1, + height: 1, + Floors: make(layerGroup, 1), + Walls: make(layerGroup, 1), + Shadows: make(layerGroup, 1), + Substitutions: make(layerGroup, 1), + } -func Test_ds1Layers_DeleteWall(t *testing.T) {} + var lg layerGroup -func Test_ds1Layers_GetFloor(t *testing.T) {} + var del func(i int) -func Test_ds1Layers_GetShadow(t *testing.T) {} + switch lt { + case floorLayerGroup: + del = func(i int) { ds1.DeleteFloor(0) } + case wallLayerGroup: + del = func(i int) { ds1.DeleteWall(0) } + case shadowLayerGroup: + del = func(i int) { ds1.DeleteShadow(0) } + case substitutionLayerGroup: + del = func(i int) { ds1.DeleteSubstitution(0) } + default: + t.Fatal("unknown layer type given") + return + } -func Test_ds1Layers_GetSubstitution(t *testing.T) {} + del(0) -func Test_ds1Layers_GetWall(t *testing.T) {} + if len(lg) > 0 { + t.Errorf("unexpected layer present after deletion") + } +} + +func Test_ds1Layers_Get(t *testing.T) { + t.Run("Floors", func(t *testing.T) { + ds1LayersGet(t, floorLayerGroup) + }) + t.Run("Walls", func(t *testing.T) { + ds1LayersGet(t, wallLayerGroup) + }) + t.Run("Shadows", func(t *testing.T) { + ds1LayersGet(t, shadowLayerGroup) + }) + t.Run("Substitution", func(t *testing.T) { + ds1LayersGet(t, substitutionLayerGroup) + }) +} + +func ds1LayersGet(t *testing.T, lt layerGroupType) { + ds1 := exampleData() + + var get func(i int) *layer + + switch lt { + case floorLayerGroup: + get = func(i int) *layer { return ds1.GetFloor(0) } + case wallLayerGroup: + get = func(i int) *layer { return ds1.GetWall(0) } + case shadowLayerGroup: + get = func(i int) *layer { return ds1.GetShadow(0) } + case substitutionLayerGroup: + get = func(i int) *layer { return ds1.GetSubstitution(0) } + default: + t.Fatal("unknown layer type given") + return + } + + layer := get(0) + + // example has nil substitution layer, maybe we need another test + if layer == nil && lt != substitutionLayerGroup { + t.Errorf("layer expected") + } +} func Test_ds1Layers_Insert(t *testing.T) { t.Run("Floors", func(t *testing.T) { @@ -85,29 +163,101 @@ func ds1LayersInsert(t *testing.T, lt layerGroupType) { } } -func Test_ds1Layers_PopFloor(t *testing.T) {} - -func Test_ds1Layers_PopShadow(t *testing.T) {} - -func Test_ds1Layers_PopSubstitution(t *testing.T) {} - -func Test_ds1Layers_PopWall(t *testing.T) {} - -func Test_ds1Layers_Push(t *testing.T) { +func Test_ds1Layers_Pop(t *testing.T) { t.Run("Floor", func(t *testing.T) { - ds1layerTest(floorLayerGroup, t) + ds1layerPop(floorLayerGroup, t) }) t.Run("Wall", func(t *testing.T) { - ds1layerTest(wallLayerGroup, t) + ds1layerPop(wallLayerGroup, t) }) t.Run("Shadow", func(t *testing.T) { - ds1layerTest(shadowLayerGroup, t) + ds1layerPop(shadowLayerGroup, t) }) t.Run("Substitution", func(t *testing.T) { - ds1layerTest(substitutionLayerGroup, t) + ds1layerPop(substitutionLayerGroup, t) + }) +} + +func ds1layerPop(lt layerGroupType, t *testing.T) { + ds1 := exampleData() + + var pop func() *layer + + var numBefore, numAfter int + + switch lt { + case floorLayerGroup: + numBefore = len(ds1.Floors) + pop = func() *layer { + l := ds1.PopFloor() + numAfter = len(ds1.Floors) + + return l + } + case wallLayerGroup: + numBefore = len(ds1.Walls) + pop = func() *layer { + l := ds1.PopWall() + numAfter = len(ds1.Walls) + + return l + } + case shadowLayerGroup: + numBefore = len(ds1.Shadows) + pop = func() *layer { + l := ds1.PopShadow() + numAfter = len(ds1.Shadows) + + return l + } + case substitutionLayerGroup: + numBefore = len(ds1.Substitutions) + pop = func() *layer { + l := ds1.PopSubstitution() + numAfter = len(ds1.Substitutions) + + return l + } + default: + t.Fatal("unknown layer type given") + return + } + + attempts := 10 + + for attempts > 0 { + attempts-- + + l := pop() + + if l == nil && numBefore < numAfter { + t.Fatal("popped nil layer, expected layer count to remain the same") + } + + if l != nil && numBefore <= numAfter { + t.Fatal("popped non-nil, expected layer count to be lower") + } + } +} + +func Test_ds1Layers_Push(t *testing.T) { + t.Run("Floor", func(t *testing.T) { + ds1layerPush(floorLayerGroup, t) + }) + + t.Run("Wall", func(t *testing.T) { + ds1layerPush(wallLayerGroup, t) + }) + + t.Run("Shadow", func(t *testing.T) { + ds1layerPush(shadowLayerGroup, t) + }) + + t.Run("Substitution", func(t *testing.T) { + ds1layerPush(substitutionLayerGroup, t) }) } @@ -115,7 +265,7 @@ func Test_ds1Layers_Push(t *testing.T) { // when we push a layer, we expect an increment, and when we push a bunch of times, // we expect to never exceed the max. we also expect to be able to retrieve a non-nil // layer after we push. -func ds1layerTest(lt layerGroupType, t *testing.T) { //nolint:funlen // no biggie +func ds1layerPush(lt layerGroupType, t *testing.T) { //nolint:funlen // no biggie layers := &ds1Layers{} // we need to set up some shit to handle the test in a generic way diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 37246152..6175a922 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -178,7 +178,6 @@ func exampleData() *DS1 { //nolint:funlen // not a big deal if this is long func version: 17, Act: 1, SubstitutionType: 0, - unknown1: make([]byte, 8), unknown2: 20, } From 51cc23abef328615727900ecd9db999fb9ade9b8 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 31 Mar 2021 00:10:21 -0700 Subject: [PATCH 31/33] revert windows path string in config defaults --- d2core/d2config/defaults.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2core/d2config/defaults.go b/d2core/d2config/defaults.go index f980770b..d6c5c52d 100644 --- a/d2core/d2config/defaults.go +++ b/d2core/d2config/defaults.go @@ -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/" From 9fe20690404780ed49e88c0004af301ec8de2109 Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 31 Mar 2021 00:14:35 -0700 Subject: [PATCH 32/33] adding size and version tests for ds1 --- d2common/d2fileformats/d2ds1/ds1_test.go | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index 6175a922..cb89e817 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -194,3 +194,29 @@ func TestDS1_MarshalUnmarshal(t *testing.T) { t.Error(loadErr) } } + +func TestDS1_Version(t *testing.T) { + ds1 := exampleData() + + v := ds1.Version() + + ds1.SetVersion(v + 1) + + if ds1.Version() == v { + t.Fatal("expected different ds1 version") + } +} + +func TestDS1_SetSize(t *testing.T) { + ds1 := exampleData() + + w, h := ds1.Size() + + ds1.SetSize(w+1, h-1) + + w2, h2 := ds1.Size() + + if w2 != (w+1) || h2 != (h-1) { + t.Fatal("unexpected width/height after setting size") + } +} From 787d7f531e39066df68ee498d57e2a1447970d4f Mon Sep 17 00:00:00 2001 From: gravestench Date: Wed, 31 Mar 2021 00:28:01 -0700 Subject: [PATCH 33/33] add layer schema test --- d2common/d2fileformats/d2ds1/ds1_test.go | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index cb89e817..2ba80a0a 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -220,3 +220,28 @@ func TestDS1_SetSize(t *testing.T) { t.Fatal("unexpected width/height after setting size") } } + +func Test_getLayerSchema(t *testing.T) { + ds1 := exampleData() + + expected := map[int]layerStreamType{ + 0: layerStreamWall1, + 1: layerStreamOrientation1, + 2: layerStreamWall2, + 3: layerStreamOrientation2, + 4: layerStreamFloor1, + 5: layerStreamShadow1, + } + + schema := ds1.getLayerSchema() + + if len(schema) != len(expected) { + t.Fatal("unexpected schema length") + } + + for idx := range expected { + if schema[idx] != expected[idx] { + t.Fatal("unexpected layer type in schema") + } + } +}