1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-04 15:00:42 +00:00

Sound engine and sound environments (#652)

* Working sound engine and sound environments

* Clean up sounds.txt loader

* Make global volume settings apply properly

Als shuffle some stuff around

* Reset sound engine on game unload
This commit is contained in:
Ziemas 2020-07-30 22:17:26 +02:00 committed by GitHub
parent 78ecc3557e
commit 29ea71489d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 374 additions and 106 deletions

View File

@ -36,11 +36,11 @@ type SoundEnvironRecord struct {
// SoundEnvirons contains the SoundEnviron records // SoundEnvirons contains the SoundEnviron records
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var SoundEnvirons map[string]*SoundEnvironRecord var SoundEnvirons map[int]*SoundEnvironRecord
// LoadSoundEnvirons loads SoundEnvirons from the supplied file // LoadSoundEnvirons loads SoundEnvirons from the supplied file
func LoadSoundEnvirons(file []byte) { func LoadSoundEnvirons(file []byte) {
SoundEnvirons = make(map[string]*SoundEnvironRecord) SoundEnvirons = make(map[int]*SoundEnvironRecord)
d := d2common.LoadDataDictionary(file) d := d2common.LoadDataDictionary(file)
for d.Next() { for d.Next() {
@ -70,7 +70,7 @@ func LoadSoundEnvirons(file []byte) {
EAXRoomRoll: d.Number("EAX Room Roll"), EAXRoomRoll: d.Number("EAX Room Roll"),
EAXAirAbsorb: d.Number("EAX Air Absorb"), EAXAirAbsorb: d.Number("EAX Air Absorb"),
} }
SoundEnvirons[record.Handle] = record SoundEnvirons[record.Index] = record
} }
if d.Err != nil { if d.Err != nil {

View File

@ -2,7 +2,6 @@ package d2datadict
import ( import (
"log" "log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
@ -12,69 +11,30 @@ type SoundEntry struct {
Handle string Handle string
Index int Index int
FileName string FileName string
Volume byte Volume int
GroupSize uint8 GroupSize int
Loop bool Loop bool
FadeIn uint8 FadeIn int
FadeOut uint8 FadeOut int
DeferInst uint8 DeferInst bool
StopInst uint8 StopInst bool
Duration uint8 Duration int
Compound int8 Compound int
Reverb bool Reverb int
Falloff uint8 Falloff int
Cache uint8 Cache bool
AsyncOnly bool AsyncOnly bool
Priority uint8 Priority int
Stream uint8 Stream bool
Stereo uint8 Stereo bool
Tracking uint8 Tracking bool
Solo uint8 Solo bool
MusicVol uint8 MusicVol bool
Block1 int Block1 int
Block2 int Block2 int
Block3 int Block3 int
} }
// CreateSoundEntry creates a sound entry based on a sound row on sounds.txt
func createSoundEntry(soundLine string) SoundEntry {
props := strings.Split(soundLine, "\t")
i := -1
inc := func() int {
i++
return i
}
result := SoundEntry{
Handle: props[inc()],
Index: d2common.StringToInt(props[inc()]),
FileName: props[inc()],
Volume: d2common.StringToUint8(props[inc()]),
GroupSize: d2common.StringToUint8(props[inc()]),
Loop: d2common.StringToUint8(props[inc()]) == 1,
FadeIn: d2common.StringToUint8(props[inc()]),
FadeOut: d2common.StringToUint8(props[inc()]),
DeferInst: d2common.StringToUint8(props[inc()]),
StopInst: d2common.StringToUint8(props[inc()]),
Duration: d2common.StringToUint8(props[inc()]),
Compound: d2common.StringToInt8(props[inc()]),
Reverb: d2common.StringToUint8(props[inc()]) == 1,
Falloff: d2common.StringToUint8(props[inc()]),
Cache: d2common.StringToUint8(props[inc()]),
AsyncOnly: d2common.StringToUint8(props[inc()]) == 1,
Priority: d2common.StringToUint8(props[inc()]),
Stream: d2common.StringToUint8(props[inc()]),
Stereo: d2common.StringToUint8(props[inc()]),
Tracking: d2common.StringToUint8(props[inc()]),
Solo: d2common.StringToUint8(props[inc()]),
MusicVol: d2common.StringToUint8(props[inc()]),
Block1: d2common.StringToInt(props[inc()]),
Block2: d2common.StringToInt(props[inc()]),
Block3: d2common.StringToInt(props[inc()]),
}
return result
}
// Sounds stores all of the SoundEntries // Sounds stores all of the SoundEntries
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var Sounds map[string]SoundEntry var Sounds map[string]SoundEntry
@ -82,31 +42,53 @@ var Sounds map[string]SoundEntry
// LoadSounds loads SoundEntries from sounds.txt // LoadSounds loads SoundEntries from sounds.txt
func LoadSounds(file []byte) { func LoadSounds(file []byte) {
Sounds = make(map[string]SoundEntry) Sounds = make(map[string]SoundEntry)
soundData := strings.Split(string(file), "\r\n")[1:]
for _, line := range soundData { d := d2common.LoadDataDictionary(file)
if line == "" { for d.Next() {
continue entry := SoundEntry{
Handle: d.String("Sound"),
Index: d.Number("Index"),
FileName: d.String("FileName"),
Volume: d.Number("Volume"),
GroupSize: d.Number("Group Size"),
Loop: d.Bool("Loop"),
FadeIn: d.Number("Fade In"),
FadeOut: d.Number("Fade Out"),
DeferInst: d.Bool("Defer Inst"),
StopInst: d.Bool("Stop Inst"),
Duration: d.Number("Duration"),
Compound: d.Number("Compound"),
Reverb: d.Number("Reverb"),
Falloff: d.Number("Falloff"),
Cache: d.Bool("Cache"),
AsyncOnly: d.Bool("Async Only"),
Priority: d.Number("Priority"),
Stream: d.Bool("Stream"),
Stereo: d.Bool("Stereo"),
Tracking: d.Bool("Tracking"),
Solo: d.Bool("Solo"),
MusicVol: d.Bool("Music Vol"),
Block1: d.Number("Block 1"),
Block2: d.Number("Block 2"),
Block3: d.Number("Block 3"),
} }
Sounds[entry.Handle] = entry
}
soundEntry := createSoundEntry(line) if d.Err != nil {
soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/") panic(d.Err)
Sounds[soundEntry.Handle] = soundEntry }
//nolint:gocritic // Debug util code
/*
// Use the following code to write out the values
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer f.Close()
if _, err := f.WriteString("\n[" + soundEntry.Handle + "] " + soundEntry.FileName); err != nil {
log.Println(err)
}
*/
} //nolint:wsl // Debug util code
log.Printf("Loaded %d sound definitions", len(Sounds)) log.Printf("Loaded %d sound definitions", len(Sounds))
} }
// SelectSoundByIndex selects a sound by its ID
func SelectSoundByIndex(index int) *SoundEntry {
for _, el := range Sounds {
if el.Index == index {
return &el
}
}
return nil
}

View File

@ -4,6 +4,6 @@ package d2interface
// by the asset manager, and set the game engine's volume levels // by the asset manager, and set the game engine's volume levels
type AudioProvider interface { type AudioProvider interface {
PlayBGM(song string) PlayBGM(song string)
LoadSoundEffect(sfx string) (SoundEffect, error) LoadSound(sfx string, loop bool, bgm bool) (SoundEffect, error)
SetVolumes(bgmVolume, sfxVolume float64) SetVolumes(bgmVolume, sfxVolume float64)
} }

View File

@ -4,4 +4,6 @@ package d2interface
type SoundEffect interface { type SoundEffect interface {
Play() Play()
Stop() Stop()
IsPlaying() bool
SetVolume(volume float64)
} }

View File

@ -290,12 +290,12 @@ const (
SFXButtonClick = "cursor_button_click" SFXButtonClick = "cursor_button_click"
SFXAmazonDeselect = "cursor_amazon_deselect" SFXAmazonDeselect = "cursor_amazon_deselect"
SFXAmazonSelect = "cursor_amazon_select" SFXAmazonSelect = "cursor_amazon_select"
SFXAssassinDeselect = "/data/global/sfx/Cursor/intro/assassin deselect.wav" SFXAssassinDeselect = "Cursor/intro/assassin deselect.wav"
SFXAssassinSelect = "/data/global/sfx/Cursor/intro/assassin select.wav" SFXAssassinSelect = "Cursor/intro/assassin select.wav"
SFXBarbarianDeselect = "cursor_barbarian_deselect" SFXBarbarianDeselect = "cursor_barbarian_deselect"
SFXBarbarianSelect = "cursor_barbarian_select" SFXBarbarianSelect = "cursor_barbarian_select"
SFXDruidDeselect = "/data/global/sfx/Cursor/intro/druid deselect.wav" SFXDruidDeselect = "Cursor/intro/druid deselect.wav"
SFXDruidSelect = "/data/global/sfx/Cursor/intro/druid select.wav" SFXDruidSelect = "Cursor/intro/druid select.wav"
SFXNecromancerDeselect = "cursor_necromancer_deselect" SFXNecromancerDeselect = "cursor_necromancer_deselect"
SFXNecromancerSelect = "cursor_necromancer_select" SFXNecromancerSelect = "cursor_necromancer_select"
SFXPaladinDeselect = "cursor_paladin_deselect" SFXPaladinDeselect = "cursor_paladin_deselect"

View File

@ -85,7 +85,7 @@ func (d *DataDictionary) List(field string) []string {
func (d *DataDictionary) Bool(field string) bool { func (d *DataDictionary) Bool(field string) bool {
n := d.Number(field) n := d.Number(field)
if n > 1 { if n > 1 {
log.Panic("Bool on non-bool field") log.Panic("Bool on non-bool field ", field)
} }
return n == 1 return n == 1

View File

@ -97,8 +97,12 @@ func (eap *AudioProvider) PlayBGM(song string) {
} }
// LoadSoundEffect loads a sound affect so that it canb e played // LoadSoundEffect loads a sound affect so that it canb e played
func (eap *AudioProvider) LoadSoundEffect(sfx string) (d2interface.SoundEffect, error) { func (eap *AudioProvider) LoadSound(sfx string, loop bool, bgm bool) (d2interface.SoundEffect, error) {
result := CreateSoundEffect(sfx, eap.audioContext, eap.sfxVolume) // TODO: Split volume := eap.sfxVolume
if bgm {
volume = eap.bgmVolume
}
result := CreateSoundEffect(sfx, eap.audioContext, volume, loop) // TODO: Split
return result, nil return result, nil
} }

View File

@ -12,40 +12,53 @@ import (
// SoundEffect represents an ebiten implementation of a sound effect // SoundEffect represents an ebiten implementation of a sound effect
type SoundEffect struct { type SoundEffect struct {
player *audio.Player player *audio.Player
volumeScale float64
} }
// CreateSoundEffect creates a new instance of ebiten's sound effect implementation. // CreateSoundEffect creates a new instance of ebiten's sound effect implementation.
func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *SoundEffect { func CreateSoundEffect(sfx string, context *audio.Context, volume float64, loop bool) *SoundEffect {
result := &SoundEffect{} result := &SoundEffect{}
var soundFile string soundFile := "/data/global/sfx/"
if _, exists := d2datadict.Sounds[sfx]; exists { if _, exists := d2datadict.Sounds[sfx]; exists {
soundEntry := d2datadict.Sounds[sfx] soundEntry := d2datadict.Sounds[sfx]
soundFile = soundEntry.FileName soundFile += soundEntry.FileName
} else { } else {
soundFile = sfx soundFile += sfx
} }
audioData, err := d2asset.LoadFile(soundFile) audioData, err := d2asset.LoadFileStream(soundFile)
if err != nil {
audioData, err = d2asset.LoadFileStream("/data/global/music/" + sfx)
}
if err != nil { if err != nil {
panic(err) panic(err)
} }
d, err := wav.Decode(context, audio.BytesReadSeekCloser(audioData)) d, err := wav.Decode(context, audioData)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
player, err := audio.NewPlayer(context, d) var player *audio.Player
if loop {
s := audio.NewInfiniteLoop(d, d.Length())
player, err = audio.NewPlayer(context, s)
} else {
player, err = audio.NewPlayer(context, d)
}
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
result.volumeScale = volume
player.SetVolume(volume) player.SetVolume(volume)
result.player = player result.player = player
@ -53,6 +66,14 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *Soun
return result return result
} }
func (v *SoundEffect) SetVolume(volume float64) {
v.player.SetVolume(volume * v.volumeScale)
}
func (v *SoundEffect) IsPlaying() bool {
return v.player.IsPlaying()
}
// Play plays the sound effect // Play plays the sound effect
func (v *SoundEffect) Play() { func (v *SoundEffect) Play() {
err := v.player.Rewind() err := v.player.Rewind()

View File

@ -0,0 +1,195 @@
package d2audio
import (
"log"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
type envState int
const (
envAttack = 0
envSustain = 1
envRelease = 2
envStopped = 3
)
const volMax float64 = 255
const originalFPS float64 = 25
// A Sound that can be started and stopped
type Sound struct {
effect d2interface.SoundEffect
entry *d2datadict.SoundEntry
volume float64
vTarget float64
vRate float64
state envState
// panning float64 // lets forget about this for now
}
func (s *Sound) update(elapsed float64) {
// attack
if s.state == envAttack {
s.volume += s.vRate * elapsed
if s.volume > s.vTarget {
s.volume = s.vTarget
s.state = envSustain
}
s.effect.SetVolume(s.volume)
}
// release
if s.state == envRelease {
s.volume -= s.vRate * elapsed
if s.volume < 0 {
s.effect.Stop()
s.volume = 0
s.state = envStopped
}
s.effect.SetVolume(s.volume)
}
}
// Play the sound
func (s *Sound) Play() {
log.Println("starting sound", s.entry.Handle)
s.effect.Play()
if s.entry.FadeIn != 0 {
s.effect.SetVolume(0)
s.volume = 0
s.state = envAttack
s.vTarget = float64(s.entry.Volume) / volMax
s.vRate = (s.vTarget / (float64(s.entry.FadeIn) / originalFPS))
} else {
s.volume = float64(s.entry.Volume) / volMax
s.effect.SetVolume(s.volume)
s.state = envSustain
}
}
// Stop the sound, only required for looping sounds
func (s *Sound) Stop() {
if s.entry.FadeOut != 0 {
s.state = envRelease
s.vTarget = 0
s.vRate = (s.volume / (float64(s.entry.FadeOut) / originalFPS))
} else {
s.state = envStopped
s.volume = 0
s.effect.SetVolume(s.volume)
s.effect.Stop()
}
}
// SoundEngine provides functions for playing sounds
type SoundEngine struct {
provider d2interface.AudioProvider
timer float64
accTime float64
sounds map[*Sound]struct{}
}
// NewSoundEngine creates a new sound engine
func NewSoundEngine(provider d2interface.AudioProvider, term d2interface.Terminal) *SoundEngine {
r := SoundEngine{
provider: provider,
sounds: map[*Sound]struct{}{},
timer: 1,
}
_ = term.BindAction("playsoundid", "plays the sound for a given id", func(id int) {
r.PlaySoundID(id)
})
_ = term.BindAction("playsound", "plays the sound for a given handle string", func(handle string) {
r.PlaySoundHandle(handle)
})
_ = term.BindAction("activesounds", "list currently active sounds", func() {
for s := range r.sounds {
log.Println(s)
}
})
_ = term.BindAction("killsounds", "kill active sounds", func() {
for s := range r.sounds {
s.Stop()
}
})
return &r
}
// Advance updates sound engine state, triggering events and envelopes
func (s *SoundEngine) Advance(elapsed float64) {
s.timer -= elapsed
s.accTime += elapsed
if s.timer < 0 {
for sound := range s.sounds {
sound.update(s.accTime)
// Clean up finished non-looping effects
if !sound.effect.IsPlaying() {
delete(s.sounds, sound)
}
// Clean up stopped looping effects
if sound.state == envStopped {
delete(s.sounds, sound)
}
}
s.timer = 0.2
s.accTime = 0
}
}
// Reset stop all sounds and reset state
func (s *SoundEngine) Reset() {
for snd := range s.sounds {
snd.effect.Stop()
delete(s.sounds, snd)
}
}
// PlaySoundID plays a sound by sounds.txt index, returning the sound here is kinda ugly
// now we could have a situation where someone holds onto the sound after the sound engine is done with it
// someone needs to be in charge of deciding when to stopping looping sounds though...
func (s *SoundEngine) PlaySoundID(id int) *Sound {
if id == 0 {
return nil
}
entry := d2datadict.SelectSoundByIndex(id)
if entry.GroupSize > 0 {
entry = d2datadict.SelectSoundByIndex(entry.Index + rand.Intn(entry.GroupSize))
}
effect, _ := s.provider.LoadSound(entry.FileName, entry.Loop, entry.MusicVol)
snd := Sound{
entry: entry,
effect: effect,
}
s.sounds[&snd] = struct{}{}
snd.Play()
return &snd
}
// PlaySoundHandle plays a sound by sounds.txt handle
func (s *SoundEngine) PlaySoundHandle(handle string) *Sound {
sound := d2datadict.Sounds[handle].Index
return s.PlaySoundID(sound)
}

View File

@ -0,0 +1,55 @@
package d2audio
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
)
type SoundEnvironment struct {
environment *d2datadict.SoundEnvironRecord
engine *SoundEngine
bgm *Sound
ambiance *Sound
eventTimer float64
}
func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment {
r := SoundEnvironment{
// Start with env NONE
environment: d2datadict.SoundEnvirons[0],
engine: soundEngine,
}
return r
}
func (s *SoundEnvironment) SetEnv(environment int) {
if s.environment.Index != environment {
newEnv := d2datadict.SoundEnvirons[environment]
if s.environment.Song != newEnv.Song {
if s.bgm != nil {
s.bgm.Stop()
}
s.bgm = s.engine.PlaySoundID(newEnv.Song)
}
if s.environment.DayAmbience != newEnv.DayAmbience {
if s.ambiance != nil {
s.ambiance.Stop()
}
s.ambiance = s.engine.PlaySoundID(newEnv.DayAmbience)
}
s.environment = newEnv
}
}
func (s *SoundEnvironment) Advance(elapsed float64) {
s.eventTimer -= elapsed
if s.eventTimer < 0 {
s.eventTimer = float64(s.environment.EventDelay) / 25
s.engine.PlaySoundID(s.environment.DayEvent)
}
}

View File

@ -33,7 +33,7 @@ type UI struct {
var singleton UI var singleton UI
func Initialize(inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider) { func Initialize(inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider) {
sfx, err := audioProvider.LoadSoundEffect(d2resource.SFXButtonClick) sfx, err := audioProvider.LoadSound(d2resource.SFXButtonClick, false, false)
if err != nil { if err != nil {
log.Fatalf("failed to initialize ui: %v", err) log.Fatalf("failed to initialize ui: %v", err)
} }

View File

@ -319,7 +319,7 @@ func (m *EscapeMenu) addEnumLabel(l *layout, optID optionID, text string, values
} }
func (m *EscapeMenu) onLoad() { func (m *EscapeMenu) onLoad() {
m.selectSound, _ = m.audioProvider.LoadSoundEffect(d2resource.SFXCursorSelect) m.selectSound, _ = m.audioProvider.LoadSound(d2resource.SFXCursorSelect, false, false)
} }
func (m *EscapeMenu) onEscKey() { func (m *EscapeMenu) onEscKey() {

View File

@ -2,14 +2,13 @@ package d2gamescreen
import ( import (
"fmt" "fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"image/color" "image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
@ -36,6 +35,8 @@ type Game struct {
lastRegionType d2enum.RegionIdType lastRegionType d2enum.RegionIdType
ticksSinceLevelCheck float64 ticksSinceLevelCheck float64
escapeMenu *EscapeMenu escapeMenu *EscapeMenu
soundEngine *d2audio.SoundEngine
soundEnv d2audio.SoundEnvironment
renderer d2interface.Renderer renderer d2interface.Renderer
inputManager d2interface.InputManager inputManager d2interface.InputManager
@ -75,7 +76,10 @@ func CreateGame(
audioProvider: audioProvider, audioProvider: audioProvider,
renderer: renderer, renderer: renderer,
terminal: term, terminal: term,
soundEngine: d2audio.NewSoundEngine(audioProvider, term),
} }
result.soundEnv = d2audio.NewSoundEnvironment(result.soundEngine)
result.escapeMenu.onLoad() result.escapeMenu.onLoad()
if err := inputManager.BindHandler(result.escapeMenu); err != nil { if err := inputManager.BindHandler(result.escapeMenu); err != nil {
@ -125,6 +129,8 @@ func (v *Game) OnUnload() error {
return err return err
} }
v.soundEngine.Reset()
return nil return nil
} }
@ -152,6 +158,8 @@ func (v *Game) Render(screen d2interface.Surface) error {
// Advance runs the update logic on the Gameplay screen // Advance runs the update logic on the Gameplay screen
func (v *Game) Advance(elapsed float64) error { func (v *Game) Advance(elapsed float64) error {
v.soundEngine.Advance(elapsed)
if (v.escapeMenu != nil && !v.escapeMenu.isOpen) || len(v.gameClient.Players) != 1 { if (v.escapeMenu != nil && !v.escapeMenu.isOpen) || len(v.gameClient.Players) != 1 {
v.gameClient.MapEngine.Advance(elapsed) // TODO: Hack v.gameClient.MapEngine.Advance(elapsed) // TODO: Hack
} }
@ -170,8 +178,7 @@ func (v *Game) Advance(elapsed float64) error {
tile := v.gameClient.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y())) tile := v.gameClient.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y()))
if tile != nil { if tile != nil {
musicInfo := d2common.GetMusicDef(tile.RegionType) v.soundEnv.SetEnv(d2datadict.LevelDetails[int(tile.RegionType)].SoundEnvironmentID)
v.audioProvider.PlayBGM(musicInfo.MusicFile)
// skip showing zone change text the first time we enter the world // skip showing zone change text the first time we enter the world
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType { if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType {
@ -203,6 +210,8 @@ func (v *Game) Advance(elapsed float64) error {
v.mapRenderer.SetCameraTarget(&position) v.mapRenderer.SetCameraTarget(&position)
} }
v.soundEnv.Advance(elapsed)
return nil return nil
} }

View File

@ -755,6 +755,6 @@ func loadSprite(animationPath string, position image.Point, playLength int, play
} }
func (v *SelectHeroClass) loadSoundEffect(sfx string) d2interface.SoundEffect { func (v *SelectHeroClass) loadSoundEffect(sfx string) d2interface.SoundEffect {
result, _ := v.audioProvider.LoadSoundEffect(sfx) result, _ := v.audioProvider.LoadSound(sfx, false, false)
return result return result
} }