From bf0412554fa861fa2b9ce885c326cbd489c99282 Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Thu, 31 Oct 2019 21:17:13 -0400 Subject: [PATCH] Big fixes. Added start of video decode. Enhanced MPQ error messages. --- Common/GameState.go | 22 ++++++ Core/Engine.go | 9 ++- MPQ/MPQ.go | 5 +- {MapEngine => Map}/DS1.go | 2 +- {MapEngine => Map}/DT1.go | 2 +- Map/Engine.go | 46 +++++++++++++ {MapEngine => Map}/Orientation.go | 2 +- {MapEngine => Map}/Region.go | 6 +- Scenes/BlizzardIntro.go | 43 ++++++++++++ Scenes/MapEngineTest.go | 17 ++--- Video/BinkDecoder.go | 111 ++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 4 ++ main.go | 1 - 14 files changed, 251 insertions(+), 20 deletions(-) create mode 100644 Common/GameState.go rename {MapEngine => Map}/DS1.go (99%) rename {MapEngine => Map}/DT1.go (99%) create mode 100644 Map/Engine.go rename {MapEngine => Map}/Orientation.go (98%) rename {MapEngine => Map}/Region.go (97%) create mode 100644 Scenes/BlizzardIntro.go create mode 100644 Video/BinkDecoder.go diff --git a/Common/GameState.go b/Common/GameState.go new file mode 100644 index 00000000..385a43ef --- /dev/null +++ b/Common/GameState.go @@ -0,0 +1,22 @@ +package Common + +import ( + "log" + "time" +) + +type GameState struct { + Seed int64 +} + +func CreateGameState() *GameState { + result := &GameState{ + Seed: time.Now().UnixNano(), + } + return result +} + +func LoadGameState(path string) *GameState { + log.Fatal("LoadGameState not implemented.") + return nil +} diff --git a/Core/Engine.go b/Core/Engine.go index 03b434b9..fab8c8f4 100644 --- a/Core/Engine.go +++ b/Core/Engine.go @@ -38,7 +38,7 @@ type EngineConfig struct { // Engine is the core OpenDiablo2 engine type Engine struct { - Settings *EngineConfig // Engine configuration settings from json file + Settings *EngineConfig // Engine configuration settings from json file Files map[string]string // Map that defines which files are in which MPQs Palettes map[Palettes.Palette]Common.Palette // Color palettes SoundEntries map[string]Sound.SoundEntry // Sound configurations @@ -72,6 +72,7 @@ func CreateEngine() *Engine { loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize() result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) result.SetNextScene(Scenes.CreateMainMenu(result, result, result.UIManager, result.SoundManager)) + //result.SetNextScene(Scenes.CreateBlizzardIntro(result, result)) return result } @@ -92,8 +93,8 @@ func (v *Engine) loadConfigurationFile() { if v.Settings.MpqPath[0] != '/' { if _, err := os.Stat(v.Settings.MpqPath); os.IsNotExist(err) { homeDir, _ := homedir.Dir() - newPath := strings.ReplaceAll(v.Settings.MpqPath, `C:\`, homeDir + "/.wine/drive_c/") - newPath = strings.ReplaceAll(newPath, "C:/", homeDir + "/.wine/drive_c/") + newPath := strings.ReplaceAll(v.Settings.MpqPath, `C:\`, homeDir+"/.wine/drive_c/") + newPath = strings.ReplaceAll(newPath, "C:/", homeDir+"/.wine/drive_c/") newPath = strings.ReplaceAll(newPath, `\`, "/") if _, err := os.Stat(newPath); !os.IsNotExist(err) { log.Printf("Detected linux wine installation, path updated to wine prefix path.") @@ -137,11 +138,13 @@ func (v *Engine) LoadFile(fileName string) []byte { mpqFile := v.Files[strings.ToLower(fileName)] mpq, err := MPQ.Load(mpqFile) if err != nil { + log.Printf("Error loading file '%s'", fileName) log.Fatal(err) } fileName = strings.ReplaceAll(fileName, `/`, `\`)[1:] blockTableEntry, err := mpq.GetFileBlockData(fileName) if err != nil { + log.Printf("Error locating block data entry for '%s' in mpq file '%s'", fileName, mpq.FileName) log.Fatal(err) } mpqStream := MPQ.CreateStream(mpq, blockTableEntry, fileName) diff --git a/MPQ/MPQ.go b/MPQ/MPQ.go index 0bd3294c..e293450a 100644 --- a/MPQ/MPQ.go +++ b/MPQ/MPQ.go @@ -11,6 +11,7 @@ import ( // MPQ represents an MPQ archive type MPQ struct { + FileName string File *os.File HashTableEntries []HashTableEntry BlockTableEntries []BlockTableEntry @@ -83,7 +84,9 @@ func (v BlockTableEntry) HasFlag(flag FileFlag) bool { // Load loads an MPQ file and returns a MPQ structure func Load(fileName string) (MPQ, error) { - result := MPQ{} + result := MPQ{ + FileName: fileName, + } file, err := os.Open(fileName) if err != nil { return MPQ{}, err diff --git a/MapEngine/DS1.go b/Map/DS1.go similarity index 99% rename from MapEngine/DS1.go rename to Map/DS1.go index 3a95a5fe..0b21b5f1 100644 --- a/MapEngine/DS1.go +++ b/Map/DS1.go @@ -1,4 +1,4 @@ -package MapEngine +package Map import ( "github.com/essial/OpenDiablo2/Common" diff --git a/MapEngine/DT1.go b/Map/DT1.go similarity index 99% rename from MapEngine/DT1.go rename to Map/DT1.go index 6b17c94e..c43a2fba 100644 --- a/MapEngine/DT1.go +++ b/Map/DT1.go @@ -1,4 +1,4 @@ -package MapEngine +package Map import ( "log" diff --git a/Map/Engine.go b/Map/Engine.go new file mode 100644 index 00000000..5242ad68 --- /dev/null +++ b/Map/Engine.go @@ -0,0 +1,46 @@ +package Map + +import ( + "image" + + "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/Sound" + "github.com/hajimehoshi/ebiten" +) + +type EngineRegion struct { + Rect image.Rectangle + Region Region +} + +type Engine struct { + soundManager *Sound.Manager + gameState *Common.GameState + fileProvider Common.FileProvider + regions []*EngineRegion +} + +func CreateMapEngine(gameState *Common.GameState, soundManager *Sound.Manager, fileProvider Common.FileProvider) *Engine { + result := &Engine{ + gameState: gameState, + soundManager: soundManager, + fileProvider: fileProvider, + regions: make([]*EngineRegion, 0), + } + return result +} + +func (v *Engine) GenerateMap(regionType RegionIdType, levelPreset int) { + //randomSource := rand.NewSource(v.gameState.Seed) + //region := LoadRegion(randomSource, regionType, levelPreset, v.fileProvider) + v.soundManager.PlayBGM("/data/global/music/Act1/tristram.wav") + v.ReloadMapCache() +} + +func (v *Engine) ReloadMapCache() { + +} + +func (v *Engine) Render(target *ebiten.Image) { + //v.region.RenderTile(300, 300, 0, 0, Map.RegionLayerTypeFloors, 0, screen) +} diff --git a/MapEngine/Orientation.go b/Map/Orientation.go similarity index 98% rename from MapEngine/Orientation.go rename to Map/Orientation.go index 0ae335c8..20d56b75 100644 --- a/MapEngine/Orientation.go +++ b/Map/Orientation.go @@ -1,4 +1,4 @@ -package MapEngine +package Map type Orientation int32 diff --git a/MapEngine/Region.go b/Map/Region.go similarity index 97% rename from MapEngine/Region.go rename to Map/Region.go index e7e08fef..ef75d8ba 100644 --- a/MapEngine/Region.go +++ b/Map/Region.go @@ -1,4 +1,4 @@ -package MapEngine +package Map import ( "log" @@ -77,9 +77,7 @@ func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileP continue } dt1 := LoadDT1("/data/global/tiles/"+levelTypeDt1, fileProvider) - for _, tileData := range dt1.Tiles { - result.Tiles = append(result.Tiles, tileData) - } + result.Tiles = append(result.Tiles, dt1.Tiles...) } levelFilesToPick := make([]string, 0) for _, fileRecord := range result.levelPreset.Files { diff --git a/Scenes/BlizzardIntro.go b/Scenes/BlizzardIntro.go new file mode 100644 index 00000000..8cf67740 --- /dev/null +++ b/Scenes/BlizzardIntro.go @@ -0,0 +1,43 @@ +package Scenes + +import ( + "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/Video" + "github.com/hajimehoshi/ebiten" +) + +type BlizzardIntro struct { + fileProvider Common.FileProvider + sceneProvider SceneProvider + videoDecoder *Video.BinkDecoder +} + +func CreateBlizzardIntro(fileProvider Common.FileProvider, sceneProvider SceneProvider) *BlizzardIntro { + result := &BlizzardIntro{ + fileProvider: fileProvider, + sceneProvider: sceneProvider, + } + + return result +} + +func (v *BlizzardIntro) Load() []func() { + return []func(){ + func() { + videoBytes := v.fileProvider.LoadFile("/data/local/video/BlizNorth640x480.bik") + v.videoDecoder = Video.CreateBinkDecoder(videoBytes) + }, + } +} + +func (v *BlizzardIntro) Unload() { + +} + +func (v *BlizzardIntro) Render(screen *ebiten.Image) { + +} + +func (v *BlizzardIntro) Update(tickTime float64) { + +} diff --git a/Scenes/MapEngineTest.go b/Scenes/MapEngineTest.go index 07a35998..d0e8c83f 100644 --- a/Scenes/MapEngineTest.go +++ b/Scenes/MapEngineTest.go @@ -1,11 +1,8 @@ package Scenes import ( - "math/rand" - "time" - "github.com/essial/OpenDiablo2/Common" - "github.com/essial/OpenDiablo2/MapEngine" + "github.com/essial/OpenDiablo2/Map" "github.com/essial/OpenDiablo2/Sound" "github.com/essial/OpenDiablo2/UI" "github.com/hajimehoshi/ebiten" @@ -16,7 +13,9 @@ type MapEngineTest struct { soundManager *Sound.Manager fileProvider Common.FileProvider sceneProvider SceneProvider - region *MapEngine.Region + //region *Map.Region + gameState *Common.GameState + mapEngine *Map.Engine } func CreateMapEngineTest(fileProvider Common.FileProvider, sceneProvider SceneProvider, uiManager *UI.Manager, soundManager *Sound.Manager) *MapEngineTest { @@ -26,16 +25,18 @@ func CreateMapEngineTest(fileProvider Common.FileProvider, sceneProvider ScenePr soundManager: soundManager, sceneProvider: sceneProvider, } + result.gameState = Common.CreateGameState() return result } func (v *MapEngineTest) Load() []func() { // TODO: Game seed comes from the game state object - randomSource := rand.NewSource(time.Now().UnixNano()) + v.soundManager.PlayBGM("") return []func(){ func() { - v.region = MapEngine.LoadRegion(randomSource, MapEngine.RegionAct1Tristram, 300, v.fileProvider) + v.mapEngine = Map.CreateMapEngine(v.gameState, v.soundManager, v.fileProvider) + v.mapEngine.GenerateMap(Map.RegionAct1Tristram, 300) }, } } @@ -45,7 +46,7 @@ func (v *MapEngineTest) Unload() { } func (v *MapEngineTest) Render(screen *ebiten.Image) { - v.region.RenderTile(300, 300, 0, 0, MapEngine.RegionLayerTypeFloors, 0, screen) + v.mapEngine.Render(screen) } func (v *MapEngineTest) Update(tickTime float64) { diff --git a/Video/BinkDecoder.go b/Video/BinkDecoder.go new file mode 100644 index 00000000..acfc0c89 --- /dev/null +++ b/Video/BinkDecoder.go @@ -0,0 +1,111 @@ +package Video + +import ( + "log" + + "github.com/essial/OpenDiablo2/Common" +) + +type BinkVideoMode uint32 + +const ( + BinkVideoModeNormal BinkVideoMode = 0 + BinkVideoModeHeightDoubled BinkVideoMode = 1 + BinkVideoModeHeightInterlaced BinkVideoMode = 2 + BinkVideoModeWidthDoubled BinkVideoMode = 3 + BinkVideoModeWidthAndHeightDoubled BinkVideoMode = 4 + BinkVideoModeWidthAndHeightInterlaced BinkVideoMode = 5 +) + +type BinkAudioAlgorithm uint32 + +const ( + BinkAudioAlgorithmFFT BinkAudioAlgorithm = 0 + BinkAudioAlgorithmDCT BinkAudioAlgorithm = 1 +) + +type BinkAudioTrack struct { + AudioChannels uint16 + AudioSampleRateHz uint16 + Stereo bool + Algorithm BinkAudioAlgorithm + AudioTrackId uint32 +} + +type BinkDecoder struct { + videoCodecRevision byte + fileSize uint32 + numberOfFrames uint32 + largestFrameSizeBytes uint32 + VideoWidth uint32 + VideoHeight uint32 + FPS uint32 + FrameTimeMS uint32 + streamReader *Common.StreamReader + VideoMode BinkVideoMode + HasAlphaPlane bool + Grayscale bool + AudioTracks []BinkAudioTrack + FrameIndexTable []uint32 // Mask bit 0, as this is defined as a keyframe + frameIndex uint32 +} + +func CreateBinkDecoder(source []byte) *BinkDecoder { + result := &BinkDecoder{ + streamReader: Common.CreateStreamReader(source), + } + result.loadHeaderInformation() + return result +} + +func (v *BinkDecoder) GetNextFrame() { + //v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE)) + lengthOfAudioPackets := v.streamReader.GetUInt32() - 4 + samplesInPacket := v.streamReader.GetUInt32() + v.streamReader.SkipBytes(int(lengthOfAudioPackets)) + log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket) + + v.frameIndex++ +} + +func (v *BinkDecoder) loadHeaderInformation() { + v.streamReader.SetPosition(0) + headerBytes, _ := v.streamReader.ReadBytes(3) + if string(headerBytes) != "BIK" { + log.Fatal("Invalid header for bink video") + } + v.videoCodecRevision = v.streamReader.GetByte() + v.fileSize = v.streamReader.GetUInt32() + v.numberOfFrames = v.streamReader.GetUInt32() + v.largestFrameSizeBytes = v.streamReader.GetUInt32() + v.streamReader.SkipBytes(4) // Number of frames again? + v.VideoWidth = v.streamReader.GetUInt32() + v.VideoHeight = v.streamReader.GetUInt32() + fpsDividend := v.streamReader.GetUInt32() + fpsDivider := v.streamReader.GetUInt32() + v.FPS = uint32(float32(fpsDividend) / float32(fpsDivider)) + v.FrameTimeMS = 1000 / v.FPS + videoFlags := v.streamReader.GetUInt32() + v.VideoMode = BinkVideoMode((videoFlags >> 28) & 0x0F) + v.HasAlphaPlane = ((videoFlags >> 20) & 0x1) == 1 + v.Grayscale = ((videoFlags >> 17) & 0x1) == 1 + numberOfAudioTracks := v.streamReader.GetUInt32() + v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks) + for i := 0; i < int(numberOfAudioTracks); i++ { + v.streamReader.SkipBytes(2) // Unknown + v.AudioTracks[i].AudioChannels = v.streamReader.GetUInt16() + } + for i := 0; i < int(numberOfAudioTracks); i++ { + v.AudioTracks[i].AudioSampleRateHz = v.streamReader.GetUInt16() + flags := v.streamReader.GetUInt16() + v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1 + v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1) + } + for i := 0; i < int(numberOfAudioTracks); i++ { + v.AudioTracks[i].AudioTrackId = v.streamReader.GetUInt32() + } + v.FrameIndexTable = make([]uint32, v.numberOfFrames+1) + for i := 0; i < int(v.numberOfFrames+1); i++ { + v.FrameIndexTable[i] = v.streamReader.GetUInt32() + } +} diff --git a/go.mod b/go.mod index bc98d89b..f6dd671a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 + github.com/giorgisio/goav v0.1.0 github.com/hajimehoshi/ebiten v1.9.3 github.com/mitchellh/go-homedir v1.1.0 ) diff --git a/go.sum b/go.sum index 66fbac91..d4eaacaa 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjsvq1Bi7DpoUjqI76VVvW045vpeFeM= github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs= +github.com/giorgisio/goav v0.1.0 h1:ZyfG3NfX7PMSimv4ulhmnQJf/XeHpMdGCn+afRmY5Oc= +github.com/giorgisio/goav v0.1.0/go.mod h1:RtH8HyxLRLU1iY0pjfhWBKRhnbsnmfoI+FxMwb5bfEo= github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -11,6 +13,8 @@ github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m2 github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ= github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= +github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= +github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/hajimehoshi/bitmapfont v1.1.1/go.mod h1:Hamfxgney7tDSmVOSDh2AWzoDH70OaC+P24zc02Gum4= github.com/hajimehoshi/ebiten v1.9.3 h1:YijWGMBwH2XA1ZytUQFy33UDHeCSS6d4JZKH1wq38O0= github.com/hajimehoshi/ebiten v1.9.3/go.mod h1:XxiJ4Eltvb1KmcD0i6F81eIB1asJhK47y5DC+FPkyso= diff --git a/main.go b/main.go index 09efb289..b38dd85e 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "log" "github.com/essial/OpenDiablo2/Core" - "github.com/essial/OpenDiablo2/MPQ" "github.com/hajimehoshi/ebiten" )