1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-12 10:40:42 +00:00

Added volume settings and sfx support

This commit is contained in:
Tim Sarbin 2019-10-26 20:09:33 -04:00
parent ec4949bc1b
commit 227e7e8b29
13 changed files with 169 additions and 61 deletions

View File

@ -3,7 +3,7 @@ go:
- 1.13.3 - 1.13.3
before_install: before_install:
- sudo apt-get -y install libx11-dev mesa-common-dev libglfw3-dev libgles2-mesa-dev libasound2-dev - sudo apt-get -y install libx11-dev mesa-common-dev libglfw3-dev libgles2-mesa-dev libasound2-dev
script: buildci.sh script: go get && golangci-lint run . && go build ./cmd/Client
git: git:
depth: 1 depth: 1
notifications: notifications:

View File

@ -50,9 +50,9 @@ func (v *BitStream) EnsureBits(bitCount int) bool {
if v.dataPosition >= len(v.data) { if v.dataPosition >= len(v.data) {
return false return false
} }
nextvalue := v.data[v.dataPosition] nextValue := v.data[v.dataPosition]
v.dataPosition++ v.dataPosition++
v.current |= int(nextvalue) << v.bitCount v.current |= int(nextValue) << v.bitCount
v.bitCount += 8 v.bitCount += 8
return true return true
} }

View File

@ -24,6 +24,14 @@ func MaxInt32(a, b int32) int32 {
return b return b
} }
// MinInt32 returns the higher of two values
func MinInt32(a, b int32) int32 {
if a < b {
return a
}
return b
}
// BytesToInt32 converts 4 bytes to int32 // BytesToInt32 converts 4 bytes to int32
func BytesToInt32(b []byte) int32 { func BytesToInt32(b []byte) int32 {
// equivalnt of return int32(binary.LittleEndian.Uint32(b)) // equivalnt of return int32(binary.LittleEndian.Uint32(b))

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io" "io"
"log"
) )
// StreamReader allows you to read data from a byte array in various formats // StreamReader allows you to read data from a byte array in various formats
@ -41,7 +42,7 @@ func (v *StreamReader) GetByte() byte {
// GetWord returns a uint16 word from the stream // GetWord returns a uint16 word from the stream
func (v *StreamReader) GetWord() uint16 { func (v *StreamReader) GetWord() uint16 {
result := uint16(v.data[v.position]) result := uint16(v.data[v.position])
result += (uint16(v.data[v.position+1]) << 8) result += uint16(v.data[v.position+1]) << 8
v.position += 2 v.position += 2
return result return result
} }
@ -49,7 +50,10 @@ func (v *StreamReader) GetWord() uint16 {
// GetSWord returns a int16 word from the stream // GetSWord returns a int16 word from the stream
func (v *StreamReader) GetSWord() int16 { func (v *StreamReader) GetSWord() int16 {
var result int16 var result int16
binary.Read(bytes.NewReader([]byte{v.data[v.position], v.data[v.position+1]}), binary.LittleEndian, &result) err := binary.Read(bytes.NewReader([]byte{v.data[v.position], v.data[v.position+1]}), binary.LittleEndian, &result)
if err != nil {
log.Panic(err)
}
v.position += 2 v.position += 2
return result return result
} }
@ -57,9 +61,9 @@ func (v *StreamReader) GetSWord() int16 {
// GetDword returns a uint32 dword from the stream // GetDword returns a uint32 dword from the stream
func (v *StreamReader) GetDword() uint32 { func (v *StreamReader) GetDword() uint32 {
result := uint32(v.data[v.position]) result := uint32(v.data[v.position])
result += (uint32(v.data[v.position+1]) << 8) result += uint32(v.data[v.position+1]) << 8
result += (uint32(v.data[v.position+2]) << 16) result += uint32(v.data[v.position+2]) << 16
result += (uint32(v.data[v.position+3]) << 24) result += uint32(v.data[v.position+3]) << 24
v.position += 4 v.position += 4
return result return result
} }

View File

@ -30,6 +30,8 @@ type EngineConfig struct {
VsyncEnabled bool VsyncEnabled bool
MpqPath string MpqPath string
MpqLoadOrder []string MpqLoadOrder []string
SfxVolume float64
BgmVolume float64
} }
// Engine is the core OpenDiablo2 engine // Engine is the core OpenDiablo2 engine
@ -59,7 +61,8 @@ func CreateEngine() *Engine {
result.loadPalettes() result.loadPalettes()
result.loadSoundEntries() result.loadSoundEntries()
result.SoundManager = Sound.CreateManager(result) result.SoundManager = Sound.CreateManager(result)
result.UIManager = UI.CreateManager(result) result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
result.UIManager = UI.CreateManager(result, *result.SoundManager)
result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading) result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading)
loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize() loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize()
result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2)))
@ -75,7 +78,10 @@ func (v *Engine) loadConfigurationFile() {
} }
var config EngineConfig var config EngineConfig
json.Unmarshal(configJSON, &config) err = json.Unmarshal(configJSON, &config)
if err != nil {
log.Fatal(err)
}
v.Settings = config v.Settings = config
} }

View File

@ -43,26 +43,26 @@ type HashTableEntry struct { // 16 bytes
type FileFlag uint32 type FileFlag uint32
const ( const (
// MpqFileImplode - File is compressed using PKWARE Data compression library // FileImplode - File is compressed using PKWARE Data compression library
MpqFileImplode FileFlag = 0x00000100 FileImplode FileFlag = 0x00000100
// MpqFileCompress - File is compressed using combination of compression methods // FileCompress - File is compressed using combination of compression methods
MpqFileCompress FileFlag = 0x00000200 FileCompress FileFlag = 0x00000200
// MpqFileEncrypted - The file is encrypted // FileEncrypted - The file is encrypted
MpqFileEncrypted FileFlag = 0x00010000 FileEncrypted FileFlag = 0x00010000
// MpqFileFixKey - The decryption key for the file is altered according to the position of the file in the archive // FileFixKey - The decryption key for the file is altered according to the position of the file in the archive
MpqFileFixKey FileFlag = 0x00020000 FileFixKey FileFlag = 0x00020000
// MpqFilePatchFile - The file contains incremental patch for an existing file in base MPQ // FilePatchFile - The file contains incremental patch for an existing file in base MPQ
MpqFilePatchFile FileFlag = 0x00100000 FilePatchFile FileFlag = 0x00100000
// MpqFileSingleUnit - Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit // FileSingleUnit - Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit
MpqFileSingleUnit FileFlag = 0x01000000 FileSingleUnit FileFlag = 0x01000000
// FileDeleteMarker - File is a deletion marker, indicating that the file no longer exists. This is used to allow patch // FileDeleteMarker - File is a deletion marker, indicating that the file no longer exists. This is used to allow patch
// archives to delete files present in lower-priority archives in the search chain. The file usually // archives to delete files present in lower-priority archives in the search chain. The file usually
// has length of 0 or 1 byte and its name is a hash // has length of 0 or 1 byte and its name is a hash
FileDeleteMarker FileFlag = 0x02000000 FileDeleteMarker FileFlag = 0x02000000
// FileSEctorCrc - File has checksums for each sector. Ignored if file is not compressed or imploded. // FileSectorCrc - File has checksums for each sector. Ignored if file is not compressed or imploded.
FileSEctorCrc FileFlag = 0x04000000 FileSectorCrc FileFlag = 0x04000000
// MpqFileExists - Set if file exists, reset when the file was deleted // FileExists - Set if file exists, reset when the file was deleted
MpqFileExists FileFlag = 0x80000000 FileExists FileFlag = 0x80000000
) )
// BlockTableEntry represents an entry in the block table // BlockTableEntry represents an entry in the block table
@ -111,9 +111,15 @@ func (v *MPQ) readHeader() error {
} }
func (v *MPQ) loadHashTable() { func (v *MPQ) loadHashTable() {
v.File.Seek(int64(v.Data.HashTableOffset), 0) _, err := v.File.Seek(int64(v.Data.HashTableOffset), 0)
if err != nil {
log.Panic(err)
}
hashData := make([]uint32, v.Data.HashTableEntries*4) hashData := make([]uint32, v.Data.HashTableEntries*4)
binary.Read(v.File, binary.LittleEndian, &hashData) err = binary.Read(v.File, binary.LittleEndian, &hashData)
if err != nil {
log.Panic(err)
}
decrypt(hashData, hashString("(hash table)", 3)) decrypt(hashData, hashString("(hash table)", 3))
for i := uint32(0); i < v.Data.HashTableEntries; i++ { for i := uint32(0); i < v.Data.HashTableEntries; i++ {
v.HashTableEntries = append(v.HashTableEntries, HashTableEntry{ v.HashTableEntries = append(v.HashTableEntries, HashTableEntry{
@ -128,9 +134,15 @@ func (v *MPQ) loadHashTable() {
} }
func (v *MPQ) loadBlockTable() { func (v *MPQ) loadBlockTable() {
v.File.Seek(int64(v.Data.BlockTableOffset), 0) _, err := v.File.Seek(int64(v.Data.BlockTableOffset), 0)
if err != nil {
log.Panic(err)
}
blockData := make([]uint32, v.Data.BlockTableEntries*4) blockData := make([]uint32, v.Data.BlockTableEntries*4)
binary.Read(v.File, binary.LittleEndian, &blockData) err = binary.Read(v.File, binary.LittleEndian, &blockData)
if err != nil {
log.Panic(err)
}
decrypt(blockData, hashString("(block table)", 3)) decrypt(blockData, hashString("(block table)", 3))
for i := uint32(0); i < v.Data.BlockTableEntries; i++ { for i := uint32(0); i < v.Data.BlockTableEntries; i++ {
v.BlockTableEntries = append(v.BlockTableEntries, BlockTableEntry{ v.BlockTableEntries = append(v.BlockTableEntries, BlockTableEntry{
@ -208,9 +220,12 @@ func (v MPQ) GetFileBlockData(fileName string) (BlockTableEntry, error) {
return v.BlockTableEntries[fileEntry.BlockIndex], nil return v.BlockTableEntries[fileEntry.BlockIndex], nil
} }
// Close closses the MPQ file // Close closes the MPQ file
func (v *MPQ) Close() { func (v *MPQ) Close() {
v.File.Close() err := v.File.Close()
if err != nil {
log.Panic(err)
}
} }
// ReadFile reads a file from the MPQ and returns a memory stream // ReadFile reads a file from the MPQ and returns a memory stream
@ -239,7 +254,7 @@ func (v MPQ) ReadTextFile(fileName string) (string, error) {
func (v *BlockTableEntry) calculateEncryptionSeed() { func (v *BlockTableEntry) calculateEncryptionSeed() {
fileName := path.Base(v.FileName) fileName := path.Base(v.FileName)
v.EncryptionSeed = hashString(fileName, 3) v.EncryptionSeed = hashString(fileName, 3)
if !v.HasFlag(MpqFileFixKey) { if !v.HasFlag(FileFixKey) {
return return
} }
v.EncryptionSeed = (v.EncryptionSeed + v.FilePosition) ^ v.UncompressedFileSize v.EncryptionSeed = (v.EncryptionSeed + v.FilePosition) ^ v.UncompressedFileSize

View File

@ -5,6 +5,7 @@ import (
"compress/zlib" "compress/zlib"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"log"
"strings" "strings"
"github.com/JoshVarga/blast" "github.com/JoshVarga/blast"
@ -34,12 +35,12 @@ func CreateStream(mpq MPQ, blockTableEntry BlockTableEntry, fileName string) *St
} }
fileSegs := strings.Split(fileName, `\`) fileSegs := strings.Split(fileName, `\`)
result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3) result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3)
if result.BlockTableEntry.HasFlag(MpqFileFixKey) { if result.BlockTableEntry.HasFlag(FileFixKey) {
result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize
} }
result.BlockSize = 0x200 << result.MPQData.Data.BlockSize result.BlockSize = 0x200 << result.MPQData.Data.BlockSize
if (result.BlockTableEntry.HasFlag(MpqFileCompress) || result.BlockTableEntry.HasFlag(MpqFileImplode)) && !result.BlockTableEntry.HasFlag(MpqFileSingleUnit) { if (result.BlockTableEntry.HasFlag(FileCompress) || result.BlockTableEntry.HasFlag(FileImplode)) && !result.BlockTableEntry.HasFlag(FileSingleUnit) {
result.loadBlockOffsets() result.loadBlockOffsets()
} }
return result return result
@ -51,7 +52,7 @@ func (v *Stream) loadBlockOffsets() {
v.MPQData.File.Seek(int64(v.BlockTableEntry.FilePosition), 0) v.MPQData.File.Seek(int64(v.BlockTableEntry.FilePosition), 0)
binary.Read(v.MPQData.File, binary.LittleEndian, &v.BlockPositions) binary.Read(v.MPQData.File, binary.LittleEndian, &v.BlockPositions)
blockPosSize := blockPositionCount << 2 blockPosSize := blockPositionCount << 2
if v.BlockTableEntry.HasFlag(MpqFileEncrypted) { if v.BlockTableEntry.HasFlag(FileEncrypted) {
decrypt(v.BlockPositions, v.EncryptionSeed-1) decrypt(v.BlockPositions, v.EncryptionSeed-1)
if v.BlockPositions[0] != blockPosSize { if v.BlockPositions[0] != blockPosSize {
panic("Decryption of MPQ failed!") panic("Decryption of MPQ failed!")
@ -63,7 +64,7 @@ func (v *Stream) loadBlockOffsets() {
} }
func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 { func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 {
if v.BlockTableEntry.HasFlag(MpqFileSingleUnit) { if v.BlockTableEntry.HasFlag(FileSingleUnit) {
return v.readInternalSingleUnit(buffer, offset, count) return v.readInternalSingleUnit(buffer, offset, count)
} }
toRead := count toRead := count
@ -94,13 +95,13 @@ func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) uin
func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 { func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 {
v.bufferData() v.bufferData()
localPosition := v.CurrentPosition % v.BlockSize localPosition := v.CurrentPosition % v.BlockSize
bytesToCopy := Common.Min(uint32(len(v.CurrentData))-localPosition, count) bytesToCopy := Common.MinInt32(int32(len(v.CurrentData))-int32(localPosition), int32(count))
if bytesToCopy <= 0 { if bytesToCopy <= 0 {
return 0 return 0
} }
copy(buffer[offset:offset+bytesToCopy], v.CurrentData[localPosition:localPosition+bytesToCopy]) copy(buffer[offset:offset+uint32(bytesToCopy)], v.CurrentData[localPosition:localPosition+uint32(bytesToCopy)])
v.CurrentPosition += bytesToCopy v.CurrentPosition += uint32(bytesToCopy)
return bytesToCopy return uint32(bytesToCopy)
} }
func (v *Stream) bufferData() { func (v *Stream) bufferData() {
@ -129,7 +130,7 @@ func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte {
offset uint32 offset uint32
toRead uint32 toRead uint32
) )
if v.BlockTableEntry.HasFlag(MpqFileCompress) || v.BlockTableEntry.HasFlag(MpqFileImplode) { if v.BlockTableEntry.HasFlag(FileCompress) || v.BlockTableEntry.HasFlag(FileImplode) {
offset = v.BlockPositions[blockIndex] offset = v.BlockPositions[blockIndex]
toRead = v.BlockPositions[blockIndex+1] - offset toRead = v.BlockPositions[blockIndex+1] - offset
} else { } else {
@ -140,21 +141,21 @@ func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte {
data := make([]byte, toRead) data := make([]byte, toRead)
v.MPQData.File.Seek(int64(offset), 0) v.MPQData.File.Seek(int64(offset), 0)
binary.Read(v.MPQData.File, binary.LittleEndian, &data) binary.Read(v.MPQData.File, binary.LittleEndian, &data)
if v.BlockTableEntry.HasFlag(MpqFileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 { if v.BlockTableEntry.HasFlag(FileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 {
if v.EncryptionSeed == 0 { if v.EncryptionSeed == 0 {
panic("Unable to determine encryption key") panic("Unable to determine encryption key")
} }
decryptBytes(data, blockIndex+v.EncryptionSeed) decryptBytes(data, blockIndex+v.EncryptionSeed)
} }
if v.BlockTableEntry.HasFlag(MpqFileCompress) && (toRead != expectedLength) { if v.BlockTableEntry.HasFlag(FileCompress) && (toRead != expectedLength) {
if !v.BlockTableEntry.HasFlag(MpqFileSingleUnit) { if !v.BlockTableEntry.HasFlag(FileSingleUnit) {
data = decompressMulti(data, expectedLength) data = decompressMulti(data, expectedLength)
} else { } else {
data = pkDecompress(data) data = pkDecompress(data)
} }
} }
if v.BlockTableEntry.HasFlag(MpqFileImplode) && (toRead != expectedLength) { if v.BlockTableEntry.HasFlag(FileImplode) && (toRead != expectedLength) {
data = pkDecompress(data) data = pkDecompress(data)
} }
@ -215,23 +216,35 @@ func decompressMulti(data []byte, expectedLength uint32) []byte {
func deflate(data []byte) []byte { func deflate(data []byte) []byte {
b := bytes.NewReader(data) b := bytes.NewReader(data)
r, err := zlib.NewReader(b) r, err := zlib.NewReader(b)
defer r.Close()
if err != nil { if err != nil {
panic(err) panic(err)
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
buffer.ReadFrom(r) _, err = buffer.ReadFrom(r)
if err != nil {
log.Panic(err)
}
err = r.Close()
if err != nil {
log.Panic(err)
}
return buffer.Bytes() return buffer.Bytes()
} }
func pkDecompress(data []byte) []byte { func pkDecompress(data []byte) []byte {
b := bytes.NewReader(data) b := bytes.NewReader(data)
r, err := blast.NewReader(b) r, err := blast.NewReader(b)
defer r.Close()
if err != nil { if err != nil {
panic(err) panic(err)
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
buffer.ReadFrom(r) _, err = buffer.ReadFrom(r)
if err != nil {
panic(err)
}
err = r.Close()
if err != nil {
panic(err)
}
return buffer.Bytes() return buffer.Bytes()
} }

View File

@ -37,6 +37,7 @@ type MainMenu struct {
copyrightLabel *UI.Label copyrightLabel *UI.Label
copyrightLabel2 *UI.Label copyrightLabel2 *UI.Label
openDiabloLabel *UI.Label openDiabloLabel *UI.Label
ShowTrademarkScreen bool ShowTrademarkScreen bool
leftButtonHeld bool leftButtonHeld bool
} }

View File

@ -14,6 +14,8 @@ type Manager struct {
audioContext *audio.Context // The Audio context audioContext *audio.Context // The Audio context
bgmAudio *audio.Player // The audio player bgmAudio *audio.Player // The audio player
lastBgm string lastBgm string
sfxVolume float64
bgmVolume float64
} }
// CreateManager creates a sound provider // CreateManager creates a sound provider
@ -21,7 +23,7 @@ func CreateManager(fileProvider Common.FileProvider) *Manager {
result := &Manager{ result := &Manager{
fileProvider: fileProvider, fileProvider: fileProvider,
} }
audioContext, err := audio.NewContext(22050) audioContext, err := audio.NewContext(44100)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -37,7 +39,10 @@ func (v *Manager) PlayBGM(song string) {
v.lastBgm = song v.lastBgm = song
go func() { go func() {
if v.bgmAudio != nil { if v.bgmAudio != nil {
v.bgmAudio.Close() err := v.bgmAudio.Close()
if err != nil {
log.Panic(err)
}
} }
audioData := v.fileProvider.LoadFile(song) audioData := v.fileProvider.LoadFile(song)
d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData)) d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData))
@ -45,13 +50,29 @@ func (v *Manager) PlayBGM(song string) {
log.Fatal(err) log.Fatal(err)
} }
s := audio.NewInfiniteLoop(d, int64(len(audioData))) s := audio.NewInfiniteLoop(d, int64(len(audioData)))
v.bgmAudio, err = audio.NewPlayer(v.audioContext, s) v.bgmAudio, err = audio.NewPlayer(v.audioContext, s)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
v.bgmAudio.SetVolume(v.bgmVolume)
// Play the infinite-length stream. This never ends. // Play the infinite-length stream. This never ends.
v.bgmAudio.Rewind() err = v.bgmAudio.Rewind()
v.bgmAudio.Play() if err != nil {
panic(err)
}
err = v.bgmAudio.Play()
if err != nil {
panic(err)
}
}() }()
} }
func (v *Manager) LoadSoundEffect(sfx string) *SoundEffect {
result := CreateSoundEffect(sfx, v.fileProvider, v.audioContext, v.sfxVolume)
return result
}
func (v *Manager) SetVolumes(bgmVolume, sfxVolume float64) {
v.sfxVolume = sfxVolume
v.bgmVolume = bgmVolume
}

38
Sound/SoundEffect.go Normal file
View File

@ -0,0 +1,38 @@
package Sound
import (
"log"
"github.com/hajimehoshi/ebiten/audio/wav"
"github.com/essial/OpenDiablo2/Common"
"github.com/hajimehoshi/ebiten/audio"
)
type SoundEffect struct {
player *audio.Player
}
func CreateSoundEffect(sfx string, fileProvider Common.FileProvider, context *audio.Context, volume float64) *SoundEffect {
result := &SoundEffect{}
audioData := fileProvider.LoadFile(sfx)
d, err := wav.Decode(context, audio.BytesReadSeekCloser(audioData))
if err != nil {
log.Fatal(err)
}
player, err := audio.NewPlayer(context, d)
if err != nil {
log.Fatal(err)
}
player.SetVolume(volume)
result.player = player
return result
}
func (v *SoundEffect) Play() {
v.player.Rewind()
v.player.Play()
}

View File

@ -4,6 +4,7 @@ import (
"github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Common"
"github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/Palettes"
"github.com/essial/OpenDiablo2/ResourcePaths" "github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/essial/OpenDiablo2/Sound"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
@ -25,14 +26,16 @@ type Manager struct {
pressedIndex int pressedIndex int
CursorX int CursorX int
CursorY int CursorY int
clickSfx *Sound.SoundEffect
} }
// CreateManager creates a new instance of a UI manager // CreateManager creates a new instance of a UI manager
func CreateManager(provider Common.FileProvider) *Manager { func CreateManager(fileProvider Common.FileProvider, soundManager Sound.Manager) *Manager {
result := &Manager{ result := &Manager{
pressedIndex: -1, pressedIndex: -1,
widgets: make([]Widget, 0), widgets: make([]Widget, 0),
cursorSprite: provider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units), cursorSprite: fileProvider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units),
clickSfx: soundManager.LoadSoundEffect(ResourcePaths.SFXButtonClick),
} }
return result return result
} }
@ -85,6 +88,7 @@ func (v *Manager) Update() {
if v.pressedIndex == -1 { if v.pressedIndex == -1 {
found = true found = true
v.pressedIndex = i v.pressedIndex = i
v.clickSfx.Play()
} else if v.pressedIndex > -1 && v.pressedIndex != i { } else if v.pressedIndex > -1 && v.pressedIndex != i {
v.widgets[i].SetPressed(false) v.widgets[i].SetPressed(false)
} else { } else {

View File

@ -1,4 +0,0 @@
#!/bin/bash
go get
golangci-lint run .
go build ./cmd/Client

View File

@ -4,6 +4,8 @@
"TicksPerSecond": 60, "TicksPerSecond": 60,
"RunInBackground": true, "RunInBackground": true,
"VsyncEnabled": true, "VsyncEnabled": true,
"SfxVolume": 1.0,
"BgmVolume": 0.3,
"MpqPath": "C:/Program Files (x86)/Diablo II", "MpqPath": "C:/Program Files (x86)/Diablo II",
"MpqLoadOrder": [ "MpqLoadOrder": [
"d2exp.mpq", "d2exp.mpq",