diff --git a/Common/StreamReader.go b/Common/StreamReader.go index bb81842c..cbb62b33 100644 --- a/Common/StreamReader.go +++ b/Common/StreamReader.go @@ -79,6 +79,23 @@ func (v *StreamReader) GetUInt32() uint32 { return result } +// GetInt32 returns an int32 word from the stream +func (v *StreamReader) GetInt32() int32 { + var result int32 + err := binary.Read(bytes.NewReader( + []byte{ + v.data[v.position], + v.data[v.position+1], + v.data[v.position+2], + v.data[v.position+3], + }), binary.LittleEndian, &result) + if err != nil { + log.Panic(err) + } + v.position += 4 + return result +} + // ReadByte implements io.ByteReader func (v *StreamReader) ReadByte() (byte, error) { return v.GetByte(), nil diff --git a/MapEngine/DS1.go b/MapEngine/DS1.go new file mode 100644 index 00000000..57546e43 --- /dev/null +++ b/MapEngine/DS1.go @@ -0,0 +1,308 @@ +package MapEngine + +import ( + "github.com/essial/OpenDiablo2/Common" +) + +var dirLookup = []int32{ + 0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06, + 0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x14, +} + +type LayerStreamType int + +const ( + LayerStreamWall1 LayerStreamType = 0 + LayerStreamWall2 LayerStreamType = 1 + LayerStreamWall3 LayerStreamType = 2 + LayerStreamWall4 LayerStreamType = 3 + LayerStreamOrientation1 LayerStreamType = 4 + LayerStreamOrientation2 LayerStreamType = 5 + LayerStreamOrientation3 LayerStreamType = 6 + LayerStreamOrientation4 LayerStreamType = 7 + LayerStreamFloor1 LayerStreamType = 8 + LayerStreamFloor2 LayerStreamType = 9 + LayerStreamShadow LayerStreamType = 10 + LayerStreamSubstitute LayerStreamType = 11 +) + +type FloorShadowRecord struct { + Prop1 byte + SubIndex byte + Unknown1 byte + MainIndex byte + Unknown2 byte + Hidden byte +} + +type WallRecord struct { + Orientation byte + Zero byte + Prop1 byte + SubIndex byte + Unknown1 byte + MainIndex byte + Unknown2 byte + Hidden byte +} + +type SubstitutionRecord struct { + Unknown uint32 +} + +type TileRecord struct { + Floors []FloorShadowRecord + Walls []WallRecord + Shadows []FloorShadowRecord + Substitutions []SubstitutionRecord +} + +type SubstitutionGroup struct { + TileX int32 + TileY int32 + WidthInTiles int32 + HeightInTiles int32 + Unknown int32 +} + +type Path struct { + X int32 + Y int32 + Action int32 +} + +type Object struct { + Type int32 + Id int32 + X int32 + Y int32 + Flags int32 + Paths []Path +} + +type DS1 struct { + 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 + Files []string // FilePtr table of file string pointers + 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 + Objects []Object // Objects + Tiles [][]TileRecord + SubstitutionGroups []SubstitutionGroup +} + +func LoadDS1(path string, fileProvider Common.FileProvider) *DS1 { + ds1 := &DS1{ + NumberOfFloors: 1, + NumberOfWalls: 1, + NumberOfShadowLayers: 1, + NumberOfSubstitutionLayers: 0, + } + fileData := fileProvider.LoadFile(path) + br := Common.CreateStreamReader(fileData) + ds1.Version = br.GetInt32() + ds1.Width = br.GetInt32() + 1 + ds1.Height = br.GetInt32() + 1 + if ds1.Version >= 8 { + ds1.Act = Common.MinInt32(5, br.GetInt32()+1) + } + if ds1.Version >= 10 { + ds1.SubstitutionType = br.GetInt32() + if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 { + ds1.NumberOfSubstitutionLayers = 1 + } + } + if ds1.Version >= 3 { + // These files reference things that don't exist anymore :-? + numberOfFiles := br.GetInt32() + ds1.Files = make([]string, numberOfFiles) + for i := 0; i < int(numberOfFiles); i++ { + ds1.Files[i] = "" + for { + ch := br.GetByte() + if ch == 0 { + break + } + ds1.Files[i] += string(ch) + } + } + } + if ds1.Version >= 9 && ds1.Version <= 13 { + // Skipping two dwords because they are "meaningless"? + _, _ = br.ReadBytes(16) + } + if ds1.Version >= 4 { + ds1.NumberOfWalls = br.GetInt32() + if ds1.Version >= 16 { + ds1.NumberOfFloors = br.GetInt32() + } else { + ds1.NumberOfFloors = 1 + } + } + var layerStream []LayerStreamType + if ds1.Version < 4 { + layerStream = []LayerStreamType{ + LayerStreamWall1, + LayerStreamFloor1, + LayerStreamOrientation1, + LayerStreamSubstitute, + LayerStreamShadow, + } + } else { + layerStream = make([]LayerStreamType, 0) + for i := 0; i < int(ds1.NumberOfWalls); i++ { + layerStream = append(layerStream, LayerStreamType(int(LayerStreamWall1)+i)) + layerStream = append(layerStream, LayerStreamType(int(LayerStreamOrientation1)+i)) + } + for i := 0; i < int(ds1.NumberOfFloors); i++ { + layerStream = append(layerStream, LayerStreamType(int(LayerStreamFloor1)+i)) + } + if ds1.NumberOfShadowLayers > 0 { + layerStream = append(layerStream, LayerStreamShadow) + } + if ds1.NumberOfSubstitutionLayers > 0 { + layerStream = append(layerStream, LayerStreamSubstitute) + } + } + 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) + } + } + for _, layerStreamType := range layerStream { + for y := 0; y < int(ds1.Height); y++ { + for x := 0; x < int(ds1.Width); x++ { + dw := br.GetUInt32() + switch layerStreamType { + case LayerStreamWall1: + fallthrough + case LayerStreamWall2: + fallthrough + case LayerStreamWall3: + fallthrough + case LayerStreamWall4: + wallIndex := int(layerStreamType) - int(LayerStreamWall1) + ds1.Tiles[y][x].Walls[wallIndex].Prop1 = byte(dw & 0x000000FF) + ds1.Tiles[y][x].Walls[wallIndex].SubIndex = byte((dw & 0x00003F00) >> 8) + ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) + ds1.Tiles[y][x].Walls[wallIndex].MainIndex = byte((dw & 0x03F00000) >> 20) + ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) + ds1.Tiles[y][x].Walls[wallIndex].Hidden = byte((dw & 0x80000000) >> 31) + case LayerStreamOrientation1: + fallthrough + case LayerStreamOrientation2: + fallthrough + case LayerStreamOrientation3: + fallthrough + case LayerStreamOrientation4: + wallIndex := int(layerStreamType) - int(LayerStreamOrientation1) + c := int32(dw & 0x000000FF) + if ds1.Version < 7 { + if c < 25 { + c = dirLookup[c] + } + } + ds1.Tiles[y][x].Walls[wallIndex].Orientation = byte(c) + ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8) + case LayerStreamFloor1: + fallthrough + case LayerStreamFloor2: + floorIndex := int(layerStreamType) - int(LayerStreamFloor1) + ds1.Tiles[y][x].Floors[floorIndex].Prop1 = byte(dw & 0x000000FF) + ds1.Tiles[y][x].Floors[floorIndex].SubIndex = byte((dw & 0x00003F00) >> 8) + ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) + ds1.Tiles[y][x].Floors[floorIndex].MainIndex = byte((dw & 0x03F00000) >> 20) + ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) + ds1.Tiles[y][x].Floors[floorIndex].Hidden = byte((dw & 0x80000000) >> 31) + case LayerStreamShadow: + ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF) + ds1.Tiles[y][x].Shadows[0].SubIndex = byte((dw & 0x00003F00) >> 8) + ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14) + ds1.Tiles[y][x].Shadows[0].MainIndex = byte((dw & 0x03F00000) >> 20) + ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26) + ds1.Tiles[y][x].Shadows[0].Hidden = byte((dw & 0x80000000) >> 31) + case LayerStreamSubstitute: + ds1.Tiles[y][x].Substitutions[0].Unknown = dw + } + } + } + } + ds1.Objects = make([]Object, 0) + if ds1.Version >= 2 { + numberOfObjects := br.GetInt32() + for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ { + newObject := Object{} + newObject.Type = br.GetInt32() + newObject.Id = br.GetInt32() + newObject.X = br.GetInt32() + newObject.Y = br.GetInt32() + newObject.Flags = br.GetInt32() + ds1.Objects = append(ds1.Objects, newObject) + } + } + ds1.SubstitutionGroups = make([]SubstitutionGroup, 0) + if ds1.Version >= 12 && (ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2) { + if ds1.Version >= 18 { + br.GetUInt32() + } + numberOfSubGroups := br.GetInt32() + for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ { + newSub := SubstitutionGroup{} + newSub.TileX = br.GetInt32() + newSub.TileY = br.GetInt32() + newSub.WidthInTiles = br.GetInt32() + newSub.HeightInTiles = br.GetInt32() + newSub.Unknown = br.GetInt32() + + ds1.SubstitutionGroups = append(ds1.SubstitutionGroups, newSub) + } + } + if ds1.Version >= 14 { + numberOfNpcs := br.GetInt32() + for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { + numPaths := br.GetInt32() + npcX := br.GetInt32() + npcY := br.GetInt32() + objIdx := -1 + for idx, ds1Obj := range ds1.Objects { + if ds1Obj.X == npcX && ds1Obj.Y == npcY { + objIdx = idx + break + } + } + if objIdx > -1 { + if ds1.Objects[objIdx].Paths == nil { + ds1.Objects[objIdx].Paths = make([]Path, numPaths) + } + for pathIdx := 0; pathIdx < int(numPaths); pathIdx++ { + newPath := Path{} + newPath.X = br.GetInt32() + newPath.Y = br.GetInt32() + if ds1.Version >= 15 { + newPath.Action = br.GetInt32() + } + ds1.Objects[objIdx].Paths[pathIdx] = newPath + } + } else { + if ds1.Version >= 15 { + _, _ = br.ReadBytes(int(numPaths) * 3) + } else { + _, _ = br.ReadBytes(int(numPaths) * 2) + } + } + } + } + return ds1 +} diff --git a/Scenes/MapEngineTest.go b/Scenes/MapEngineTest.go index 6f0543ec..5d3f2614 100644 --- a/Scenes/MapEngineTest.go +++ b/Scenes/MapEngineTest.go @@ -2,6 +2,7 @@ package Scenes import ( "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/MapEngine" "github.com/essial/OpenDiablo2/Sound" "github.com/essial/OpenDiablo2/UI" "github.com/hajimehoshi/ebiten" @@ -25,7 +26,12 @@ func CreateMapEngineTest(fileProvider Common.FileProvider, sceneProvider ScenePr } func (v *MapEngineTest) Load() []func() { - return []func(){} + v.soundManager.PlayBGM("") + return []func(){ + func() { + _ = MapEngine.LoadDS1("/data/global/tiles/ACT1/town/townE1.ds1", v.fileProvider) + }, + } } func (v *MapEngineTest) Unload() { diff --git a/Sound/AudioProvider.go b/Sound/AudioProvider.go index 54724232..380ec780 100644 --- a/Sound/AudioProvider.go +++ b/Sound/AudioProvider.go @@ -37,6 +37,10 @@ func (v *Manager) PlayBGM(song string) { return } v.lastBgm = song + if song == "" && v.bgmAudio != nil && v.bgmAudio.IsPlaying() { + _ = v.bgmAudio.Pause() + return + } go func() { if v.bgmAudio != nil { err := v.bgmAudio.Close()