1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-09-29 22:56:07 -04:00

Merge pull request #1066 from gravestench/d2ds1_refactor

Refactoring d2ds1
This commit is contained in:
gravestench 2021-03-31 01:54:07 -07:00 committed by GitHub
commit 59114a72ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1716 additions and 485 deletions

View File

@ -38,7 +38,7 @@ func (v *StreamWriter) PushBytes(b ...byte) {
// use another Push... method, bits'll not be pushed
func (v *StreamWriter) PushBit(b bool) {
if b {
v.bitCache |= (1 << v.bitOffset)
v.bitCache |= 1 << v.bitOffset
}
v.bitOffset++

View File

@ -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
)

View File

@ -232,7 +232,7 @@ func (ad *AnimationData) Marshal() []byte {
recordIdx := 0
// numberOfEntries is a number of entries in all map indexes
var numberOfEntries int = 0
var numberOfEntries = 0
for i := 0; i < len(keys); i++ {
numberOfEntries += len(ad.entries[keys[i]])

View File

@ -1,2 +1,2 @@
// Package d2ds1 provides functionality for loading/processing DS1 files
// Package d2ds1 provides functionality for loading/processing DS1 Files
package d2ds1

View File

@ -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"
@ -11,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
)
@ -39,101 +27,41 @@ 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
*ds1Layers
Files []string // FilePtr table of file string pointers
Objects []Object // Objects
substitutionGroups []SubstitutionGroup // Substitution groups for the DS1
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 [unknown1BytesCount]byte
unknown2 uint32
}
// LoadDS1 loads the specified DS1 file
func LoadDS1(fileData []byte) (*DS1, error) {
ds1 := &DS1{
Act: 1,
NumberOfFloors: 0,
NumberOfWalls: 0,
NumberOfShadowLayers: 1,
NumberOfSubstitutionLayers: 0,
const (
defaultNumFloors = 1
defaultNumShadows = maxShadowLayers
defaultNumSubstitutions = 0
)
// Unmarshal the given bytes to a DS1 struct
func Unmarshal(fileData []byte) (*DS1, error) {
return (&DS1{}).Unmarshal(fileData)
}
// Unmarshal the given bytes to a DS1 struct
func (ds1 *DS1) Unmarshal(fileData []byte) (*DS1, error) {
ds1.ds1Layers = &ds1Layers{}
stream := d2datautils.CreateStreamReader(fileData)
if err := ds1.loadHeader(stream); err != nil {
return nil, fmt.Errorf("loading header: %w", err)
}
br := d2datautils.CreateStreamReader(fileData)
var err error
err = ds1.loadHeader(br)
if err != nil {
return nil, 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
}
}
if ds1.Version >= v4 {
ds1.NumberOfWalls, err = br.ReadInt32()
if err != nil {
return nil, err
}
if ds1.Version >= v16 {
ds1.NumberOfFloors, err = br.ReadInt32()
if err != nil {
return nil, err
}
} else {
ds1.NumberOfFloors = 1
}
}
ds1.LayerStreamTypes = ds1.setupStreamLayerTypes()
ds1.Tiles = make([][]TileRecord, 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)
}
}
err = ds1.loadLayerStreams(br)
if err != nil {
return nil, err
}
err = ds1.loadObjects(br)
if err != nil {
return nil, err
}
err = ds1.loadSubstitutions(br)
if err != nil {
return nil, err
}
err = ds1.loadNPCs(br)
if err != nil {
return nil, err
if err := ds1.loadBody(stream); err != nil {
return nil, fmt.Errorf("loading body: %w", err)
}
return ds1, nil
@ -142,77 +70,157 @@ 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 err
return fmt.Errorf("reading version: %w", err)
}
ds1.Width, err = br.ReadInt32()
ds1.version = ds1version(v)
width, err = br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading width: %w", err)
}
ds1.Height, err = br.ReadInt32()
height, err = br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading height: %w", err)
}
ds1.Width++
ds1.Height++
width++
height++
if ds1.Version >= v8 {
ds1.SetSize(int(width), int(height))
if ds1.version.specifiesAct() {
ds1.Act, err = br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading Act: %w", err)
}
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 err
return fmt.Errorf("reading substitution type: %w", err)
}
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
ds1.NumberOfSubstitutionLayers = 1
switch ds1.SubstitutionType {
case subType1, subType2:
ds1.PushSubstitution(&layer{})
}
}
err = ds1.loadFileList(br)
if err != nil {
return err
return fmt.Errorf("loading file list: %w", err)
}
return nil
}
func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error {
var numWalls, numFloors, numShadows, numSubstitutions int32
numFloors = defaultNumFloors
numShadows = defaultNumShadows
numSubstitutions = defaultNumSubstitutions
if ds1.version.hasUnknown1Bytes() {
var err error
bytes, err := stream.ReadBytes(unknown1BytesCount)
if err != nil {
return fmt.Errorf("reading unknown1: %w", err)
}
copy(ds1.unknown1[:], bytes[:unknown1BytesCount])
}
if ds1.version.specifiesWalls() {
var err error
numWalls, err = stream.ReadInt32()
if err != nil {
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: %w", err)
}
}
}
for ; numWalls > 0; numWalls-- {
ds1.PushWall(&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)
if err := ds1.loadLayerStreams(stream); err != nil {
return fmt.Errorf("loading layer streams: %w", err)
}
if err := ds1.loadObjects(stream); err != nil {
return fmt.Errorf("loading Objects: %w", err)
}
if err := ds1.loadSubstitutions(stream); err != nil {
return fmt.Errorf("loading Substitutions: %w", err)
}
if err := ds1.loadNPCs(stream); err != nil {
return fmt.Errorf("loading npc's: %w", err)
}
return nil
}
func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
if ds1.Version >= v3 {
// These files reference things that don't exist anymore :-?
numberOfFiles, err := br.ReadInt32()
if err != nil {
return err
}
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: %w", 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 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: %w", err)
}
if ch == 0 {
break
}
ds1.Files[i] += string(ch)
}
}
@ -220,51 +228,53 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
}
func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
if ds1.Version < v2 {
if !ds1.version.hasObjects() {
ds1.Objects = make([]Object, 0)
} else {
numberOfObjects, err := br.ReadInt32()
return nil
}
numObjects, err := br.ReadInt32()
if err != nil {
return fmt.Errorf("reading number of Objects: %w", err)
}
ds1.Objects = make([]Object, numObjects)
for objIdx := 0; objIdx < int(numObjects); objIdx++ {
obj := Object{}
objType, err := br.ReadInt32()
if err != nil {
return 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 err
}
objID, err := br.ReadInt32()
if err != nil {
return err
}
objX, err := br.ReadInt32()
if err != nil {
return err
}
objY, err := br.ReadInt32()
if err != nil {
return err
}
objFlags, err := br.ReadInt32()
if err != nil {
return 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
@ -273,94 +283,108 @@ 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)
ds1.substitutionGroups = make([]SubstitutionGroup, 0)
return nil
}
if ds1.Version >= v18 {
if ds1.version.hasUnknown2Bytes() {
ds1.unknown2, err = br.ReadUInt32()
if err != nil {
return err
return fmt.Errorf("reading unknown 2: %w", err)
}
}
numberOfSubGroups, err := br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading number of sub groups: %w", err)
}
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
ds1.substitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ {
newSub := SubstitutionGroup{}
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
ds1.substitutionGroups[subIdx] = newSub
}
return err
}
func (ds1 *DS1) setupStreamLayerTypes() []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,
if ds1.version.hasStandardLayers() {
layerStream = []layerStreamType{
layerStreamWall1,
layerStreamFloor1,
layerStreamOrientation1,
layerStreamSubstitute1,
layerStreamShadow1,
}
} else {
// nolint:gomnd // constant
layerStream = make([]d2enum.LayerStreamType,
(ds1.NumberOfWalls*2)+ds1.NumberOfFloors+ds1.NumberOfShadowLayers+ds1.NumberOfSubstitutionLayers)
layerIdx := 0
for i := 0; i < int(ds1.NumberOfWalls); 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++ {
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
@ -369,29 +393,29 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType {
func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
var err error
if ds1.Version < v14 {
return err
if !ds1.version.specifiesNPCs() {
return nil
}
numberOfNpcs, err := br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading number of npcs: %w", 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
@ -399,7 +423,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
}
@ -408,10 +431,10 @@ 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 {
if ds1.version.specifiesNPCActions() {
br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data
} else {
br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data
@ -423,8 +446,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)
}
@ -434,20 +455,20 @@ 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))
if ds1.Version >= v15 {
if ds1.version.specifiesNPCActions() {
action, err := br.ReadInt32()
if err != nil {
return err
return fmt.Errorf("reading action for path %d: %v", pathIdx, err)
}
newPath.Action = int(action)
@ -456,58 +477,62 @@ 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{
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.getLayerSchema()
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...
for _, layerStreamType := range layerStreamTypes {
for y := 0; y < ds1.height; y++ {
for x := 0; x < ds1.width; x++ {
dw, err := br.ReadUInt32()
if err != nil {
return err
return fmt.Errorf("reading layer's dword: %w", 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 {
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)
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.Walls[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.Shadows[0].Tile(x, y).DecodeShadow(dw)
case layerStreamSubstitute1:
ds1.Substitutions[0].Tile(x, y).Substitution = dw
}
}
}
}
return err
return nil
}
// SetSize sets the size of all layers in the DS1
func (ds1 *DS1) SetSize(w, h int) {
ds1.ds1Layers.SetSize(w, h)
}
// Marshal encodes ds1 back to byte slice
@ -516,19 +541,19 @@ 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(int32(ds1.version))
sw.PushInt32(int32(ds1.width - 1))
sw.PushInt32(int32(ds1.height - 1))
if ds1.Version >= v8 {
if ds1.version.specifiesAct() {
sw.PushInt32(ds1.Act - 1)
}
if ds1.Version >= v10 {
if ds1.version.specifiesSubstitutionType() {
sw.PushInt32(ds1.SubstitutionType)
}
if ds1.Version >= v3 {
if ds1.version.hasFileList() {
sw.PushInt32(int32(len(ds1.Files)))
for _, i := range ds1.Files {
@ -539,23 +564,23 @@ func (ds1 *DS1) Marshal() []byte {
}
}
if ds1.Version >= v9 && ds1.Version <= v13 {
sw.PushBytes(ds1.unknown1...)
if ds1.version.hasUnknown1Bytes() {
sw.PushBytes(ds1.unknown1[:]...)
}
if ds1.Version >= v4 {
sw.PushInt32(ds1.NumberOfWalls)
if ds1.version.specifiesWalls() {
sw.PushInt32(int32(len(ds1.Walls)))
if ds1.Version >= v16 {
sw.PushInt32(ds1.NumberOfFloors)
if ds1.version.specifiesFloors() {
sw.PushInt32(int32(len(ds1.Walls)))
}
}
// Step 2 - encode layers
// Step 2 - encode grid
ds1.encodeLayers(sw)
// Step 3 - encode objects
if !(ds1.Version < v2) {
// Step 3 - encode Objects
if ds1.version.hasObjects() {
sw.PushInt32(int32(len(ds1.Objects)))
for _, i := range ds1.Objects {
@ -567,13 +592,16 @@ func (ds1 *DS1) Marshal() []byte {
}
}
// Step 4 - encode substitutions
if ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) {
// 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)))
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 +617,31 @@ func (ds1 *DS1) Marshal() []byte {
}
func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) {
for lIdx := range ds1.LayerStreamTypes {
layerStreamType := ds1.LayerStreamTypes[lIdx]
layerStreamTypes := ds1.getLayerSchema()
for y := 0; y < int(ds1.Height); y++ {
for x := 0; x < int(ds1.Width); x++ {
for _, layerStreamType := range layerStreamTypes {
for y := 0; y < ds1.height; y++ {
for x := 0; x < 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
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 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)
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)
}
}
}
@ -621,22 +649,44 @@ 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))
if ds1.version >= v15 {
sw.PushUint32(uint32(path.Action))
}
}
}
}
// 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)
}

View File

@ -0,0 +1,348 @@
package d2ds1
const (
maxWallLayers = 4
maxFloorLayers = 2
maxShadowLayers = 1
maxSubstitutionLayers = 1
)
type layerGroupType int
const (
floorLayerGroup layerGroupType = iota
wallLayerGroup
shadowLayerGroup
substitutionLayerGroup
)
type layerGroup []*layer
type ds1Layers struct {
width, height int
Floors layerGroup
Walls 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.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(shadowLayerGroup)
l.cullNilLayers(substitutionLayerGroup)
}
// removes nil layers of given layer group type
func (l *ds1Layers) cullNilLayers(t layerGroupType) {
group := l.getLayersGroup(t)
if group == nil {
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(shadowLayerGroup)
l.enforceSize(substitutionLayerGroup)
}
func (l *ds1Layers) enforceSize(t layerGroupType) {
l.ensureInit()
l.cull()
group := l.getLayersGroup(t)
if group == nil {
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()
group := l.getLayersGroup(t)
max := getMaxGroupLen(t)
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()
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
theLayer = (*group)[lastIdx]
*group = (*group)[:lastIdx]
return theLayer
}
return nil
}
func (l *ds1Layers) get(t layerGroupType, idx int) *layer {
l.ensureInit()
l.cull()
group := l.getLayersGroup(t)
if group == nil {
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
}
group := l.getLayersGroup(t)
if group == nil {
return
}
if len(*group)+1 > getMaxGroupLen(t) {
return
}
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:
// suppose
// idx=1
// newLayer=c
// existing layerGroup is [a, b]
newGroup := append((*group)[:idx], append([]*layer{newLayer}, (*group)[idx:]...)...)
*group = newGroup
}
func (l *ds1Layers) delete(t layerGroupType, idx int) {
l.ensureInit()
l.cull()
group := l.getLayersGroup(t)
if group == nil {
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) 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(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
}

View File

@ -0,0 +1,333 @@
package d2ds1
import (
"testing"
)
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 ds1LayersDelete(t *testing.T, lt layerGroupType) {
ds1 := DS1{}
ds1.ds1Layers = &ds1Layers{
width: 1,
height: 1,
Floors: make(layerGroup, 1),
Walls: make(layerGroup, 1),
Shadows: make(layerGroup, 1),
Substitutions: make(layerGroup, 1),
}
var lg layerGroup
var del func(i int)
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
}
del(0)
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) {
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 ds1LayersInsert(t *testing.T, lt layerGroupType) {
ds1 := DS1{}
layers := make([]*layer, getMaxGroupLen(lt)+1)
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)
}
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_Pop(t *testing.T) {
t.Run("Floor", func(t *testing.T) {
ds1layerPop(floorLayerGroup, t)
})
t.Run("Wall", func(t *testing.T) {
ds1layerPop(wallLayerGroup, t)
})
t.Run("Shadow", func(t *testing.T) {
ds1layerPop(shadowLayerGroup, t)
})
t.Run("Substitution", func(t *testing.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)
})
}
// 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 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
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 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()
check(max)
}

View File

@ -0,0 +1,247 @@
package d2ds1
import (
"testing"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
)
func exampleData() *DS1 { //nolint:funlen // not a big deal if this is long func
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: 2,
height: 2,
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,
unknown2: 20,
}
return result
}
func TestDS1_MarshalUnmarshal(t *testing.T) {
ds1 := exampleData()
data := ds1.Marshal()
_, loadErr := Unmarshal(data)
if loadErr != nil {
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")
}
}
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")
}
}
}

View File

@ -0,0 +1,72 @@
package d2ds1
type ds1version int
const (
v3 ds1version = 3
v4 ds1version = 4
v7 ds1version = 7
v8 ds1version = 8
v9 ds1version = 9
v10 ds1version = 10
v12 ds1version = 12
v13 ds1version = 13
v14 ds1version = 14
v15 ds1version = 15
v16 ds1version = 16
v18 ds1version = 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) 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
}
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
}
func (v ds1version) specifiesNPCs() bool {
return v > v14
}
func (v ds1version) specifiesNPCActions() bool {
return v > v15
}

View File

@ -1,69 +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
)
// FloorShadowRecord represents a floor or shadow record in a DS1 file.
type FloorShadowRecord struct {
Prop1 byte
Sequence byte
Unknown1 byte
Style byte
Unknown2 byte
HiddenBytes byte
RandomIndex byte
Animated bool
YAdjust int
}
// Hidden returns if floor/shadow is hidden
func (f *FloorShadowRecord) Hidden() bool {
return f.HiddenBytes > 0
}
// Decode decodes floor-shadow record
func (f *FloorShadowRecord) Decode(dw uint32) {
f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
f.Style = byte((dw & styleBitmask) >> styleOffset)
f.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
f.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
}
// Encode adds Floor's bits to stream writter given
func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) {
sw.PushBits32(uint32(f.Prop1), prop1Length)
sw.PushBits32(uint32(f.Sequence), sequenceLength)
sw.PushBits32(uint32(f.Unknown1), unknown1Length)
sw.PushBits32(uint32(f.Style), styleLength)
sw.PushBits32(uint32(f.Unknown2), unknown2Length)
sw.PushBits32(uint32(f.HiddenBytes), hiddenLength)
}

View File

@ -0,0 +1,129 @@
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 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) *layer {
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) *layer {
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) *layer {
return l.SetWidth(w).SetHeight(h)
}

View File

@ -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())
}
}

View File

@ -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)
}

View File

@ -1,6 +0,0 @@
package d2ds1
// SubstitutionRecord represents a substitution record in a DS1 file.
type SubstitutionRecord struct {
Unknown uint32
}

View File

@ -0,0 +1,127 @@
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 {
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
}
type tileWallFields struct {
Type d2enum.TileType
Zero byte
}
// Tile represents a tile record in a DS1 file.
type Tile struct {
tileCommonFields
tileSubstitutionFields
tileWallFields
tileFloorShadowFields
}
// 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)
}

View File

@ -1,9 +0,0 @@
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
}

View File

@ -1,45 +0,0 @@
package d2ds1
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
// WallRecord represents a wall record.
type WallRecord 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 *WallRecord) Hidden() bool {
return w.HiddenBytes > 0
}
// Decode decodes wall record
func (w *WallRecord) Decode(dw uint32) {
w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
w.Style = byte((dw & styleBitmask) >> styleOffset)
w.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
w.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
}
// Encode adds wall's record's bytes into stream writer given
func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) {
sw.PushBits32(uint32(w.Prop1), prop1Length)
sw.PushBits32(uint32(w.Sequence), sequenceLength)
sw.PushBits32(uint32(w.Unknown1), unknown1Length)
sw.PushBits32(uint32(w.Style), styleLength)
sw.PushBits32(uint32(w.Unknown2), unknown2Length)
sw.PushBits32(uint32(w.HiddenBytes), hiddenLength)
}

View File

@ -517,9 +517,9 @@ 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
return nil, fmt.Errorf("loading ds1 file %s: %v", "/data/global/tiles"+ds1Path, err)
}
if err := am.dt1s.Insert(ds1Path, ds1, defaultCacheEntryWeight); err != nil {

View File

@ -388,7 +388,7 @@ func (box *Box) setupTitle(sectionHeight int) error {
}
func (box *Box) setupOptions(sectionHeight int) error {
box.contentLayout.SetSize(box.width, (box.height - sectionHeight))
box.contentLayout.SetSize(box.width, box.height-sectionHeight)
if !box.disableBorder {
cornerLeft, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)

View File

@ -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 {

View File

@ -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.TileRecord
Components d2mapstamp.Tile
RegionType d2enum.RegionIdType
SubTiles [25]d2dt1.SubTileFlags
}

View File

@ -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.FloorShadowRecord{{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)
}
}

View File

@ -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.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.FloorShadowRecord, target d2interf
target.Render(img)
}
func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, 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.WallRecord, viewport *Viewport, tar
target.Render(img)
}
func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, 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)

View File

@ -53,7 +53,7 @@ func (mr *MapRenderer) generateTileCache() {
}
}
func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) {
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.FloorShadowRecord) {
}
}
func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) {
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.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.Tile) {
tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), tile.Type)
var tileData *d2dt1.Tile

View File

@ -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

View File

@ -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.
@ -54,9 +54,42 @@ func (mr *Stamp) RegionPath() string {
return mr.regionPath
}
// Tile represents a map tile, which can have a variable amount of floors, walls, shadows as layers.
// Typically, there will be an Orientation layer for each wall layer.
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.TileRecord {
return &mr.ds1.Tiles[y][x]
func (mr *Stamp) Tile(x, y int) *Tile {
t := &Tile{
Walls: make([]d2ds1.Tile, len(mr.ds1.Walls)),
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.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.
@ -109,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)
}

View File

@ -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"),

View File

@ -8,11 +8,11 @@ import (
// GitBranch is set by the CI build process to the name of the branch
//nolint:gochecknoglobals // This is filled in by the build system
var GitBranch string = "local"
var GitBranch = "local"
// GitCommit is set by the CI build process to the commit hash
//nolint:gochecknoglobals // This is filled in by the build system
var GitCommit string = "build"
var GitCommit = "build"
func main() {
log.SetFlags(log.Lshortfile)

View File

@ -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