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 { ++ ++}