Merge pull request #1026 from gravestench/ecs

merge upstream master into ecs branch
This commit is contained in:
gravestench 2021-01-11 04:32:51 +00:00 committed by GitHub
commit 1409166bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 697 additions and 508 deletions

View File

@ -15,20 +15,18 @@ import (
"os/signal"
"path/filepath"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"sync"
"syscall"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/pkg/profile"
"golang.org/x/image/colornames"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
ebiten2 "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio/ebiten"
@ -260,7 +258,7 @@ func (a *App) LoadConfig() (*d2config.Configuration, error) {
return nil, err
}
config.SetPath(filepath.Join(configAsset.Source().Path(), configAsset.Path()))
//config.SetPath(filepath.Join(configAsset.Source().Path(), configAsset.Path()))
a.Infof("loaded configuration file from %s", config.Path())
@ -270,8 +268,8 @@ func (a *App) LoadConfig() (*d2config.Configuration, error) {
// Run executes the application and kicks off the entire game process
func (a *App) Run() (err error) {
// add our possible config directories
_, _ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()))
_, _ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()))
_ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()), types.AssetSourceFileSystem)
_ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()), types.AssetSourceFileSystem)
if a.config, err = a.LoadConfig(); err != nil {
return err
@ -322,192 +320,6 @@ func (a *App) Run() (err error) {
return nil
}
func (a *App) initialize() error {
if err := a.initConfig(a.config); err != nil {
return err
}
a.initLanguage()
if err := a.initDataDictionaries(); err != nil {
return err
}
a.timeScale = 1.0
a.lastTime = d2util.Now()
a.lastScreenAdvance = a.lastTime
a.renderer.SetWindowIcon("d2logo.png")
a.terminal.BindLogger()
terminalCommands := []struct {
name string
desc string
args []string
fn func(args []string) error
}{
{"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap},
{"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen},
{"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame},
{"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture},
{"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture},
{"vsync", "toggles vsync", nil, a.toggleVsync},
{"fps", "toggle fps counter", nil, a.toggleFpsCounter},
{"timescale", "set scalar for elapsed time", []string{"float"}, a.setTimeScale},
{"quit", "exits the game", nil, a.quitGame},
{"screen-gui", "enters the gui playground screen", nil, a.enterGuiPlayground},
{"js", "eval JS scripts", []string{"code"}, a.evalJS},
}
for _, cmd := range terminalCommands {
if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil {
a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error())
}
}
gui, err := d2gui.CreateGuiManager(a.asset, *a.Options.LogLevel, a.inputManager)
if err != nil {
return err
}
a.guiManager = gui
a.screen = d2screen.NewScreenManager(a.ui, *a.Options.LogLevel, a.guiManager)
a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume)
if err := a.loadStrings(); err != nil {
return err
}
a.ui.Initialize()
return nil
}
const (
fmtErrSourceNotFound = `file not found: %s
Please check your config file at %s
Also, verify that the MPQ files exist at %s
Capitalization in the file name matters.
`
)
func (a *App) initConfig(config *d2config.Configuration) error {
a.config = config
for _, mpqName := range a.config.MpqLoadOrder {
cleanDir := filepath.Clean(a.config.MpqPath)
srcPath := filepath.Join(cleanDir, mpqName)
_, err := a.asset.AddSource(srcPath)
if err != nil {
// nolint:stylecheck // we want a multiline error message here..
return fmt.Errorf(fmtErrSourceNotFound, srcPath, a.config.Path(), a.config.MpqPath)
}
}
return nil
}
func (a *App) initLanguage() {
a.language = a.asset.LoadLanguage(d2resource.LocalLanguage)
a.asset.Loader.SetLanguage(&a.language)
a.charset = d2resource.GetFontCharset(a.language)
a.asset.Loader.SetCharset(&a.charset)
}
func (a *App) initDataDictionaries() error {
dictPaths := []string{
d2resource.LevelType, d2resource.LevelPreset, d2resource.LevelWarp,
d2resource.ObjectType, d2resource.ObjectDetails, d2resource.Weapons,
d2resource.Armor, d2resource.Misc, d2resource.Books, d2resource.ItemTypes,
d2resource.UniqueItems, d2resource.Missiles, d2resource.SoundSettings,
d2resource.MonStats, d2resource.MonStats2, d2resource.MonPreset,
d2resource.MonProp, d2resource.MonType, d2resource.MonMode,
d2resource.MagicPrefix, d2resource.MagicSuffix, d2resource.ItemStatCost,
d2resource.ItemRatio, d2resource.StorePage, d2resource.Overlays,
d2resource.CharStats, d2resource.Hireling, d2resource.Experience,
d2resource.Gems, d2resource.QualityItems, d2resource.Runes,
d2resource.DifficultyLevels, d2resource.AutoMap, d2resource.LevelDetails,
d2resource.LevelMaze, d2resource.LevelSubstitutions, d2resource.CubeRecipes,
d2resource.SuperUniques, d2resource.Inventory, d2resource.Skills,
d2resource.SkillCalc, d2resource.MissileCalc, d2resource.Properties,
d2resource.SkillDesc, d2resource.BodyLocations, d2resource.Sets,
d2resource.SetItems, d2resource.AutoMagic, d2resource.TreasureClass,
d2resource.TreasureClassEx, d2resource.States, d2resource.SoundEnvirons,
d2resource.Shrines, d2resource.ElemType, d2resource.PlrMode,
d2resource.PetType, d2resource.NPC, d2resource.MonsterUniqueModifier,
d2resource.MonsterEquipment, d2resource.UniqueAppellation, d2resource.MonsterLevel,
d2resource.MonsterSound, d2resource.MonsterSequence, d2resource.PlayerClass,
d2resource.MonsterPlacement, d2resource.ObjectGroup, d2resource.CompCode,
d2resource.MonsterAI, d2resource.RarePrefix, d2resource.RareSuffix,
d2resource.Events, d2resource.Colors, d2resource.ArmorType,
d2resource.WeaponClass, d2resource.PlayerType, d2resource.Composite,
d2resource.HitClass, d2resource.UniquePrefix, d2resource.UniqueSuffix,
d2resource.CubeModifier, d2resource.CubeType, d2resource.HirelingDescription,
d2resource.LowQualityItems,
}
a.Info("Initializing asset manager")
for _, path := range dictPaths {
err := a.asset.LoadRecords(path)
if err != nil {
return err
}
}
err := a.initAnimationData(d2resource.AnimationData)
if err != nil {
return err
}
return nil
}
const (
fmtLoadAnimData = "loading animation data from: %s"
)
func (a *App) initAnimationData(path string) error {
animDataBytes, err := a.asset.LoadFile(path)
if err != nil {
return err
}
a.Debugf(fmtLoadAnimData, path)
animData := d2data.LoadAnimationData(animDataBytes)
a.Infof("Loaded %d animation data records", len(animData))
a.asset.Records.Animation.Data = animData
return nil
}
func (a *App) loadStrings() error {
tablePaths := []string{
d2resource.PatchStringTable,
d2resource.ExpansionStringTable,
d2resource.StringTable,
}
for _, tablePath := range tablePaths {
_, err := a.asset.LoadStringTable(tablePath)
if err != nil {
return err
}
}
return nil
}
func (a *App) renderDebug(target d2interface.Surface) {
if !a.showFPS {
return
@ -636,57 +448,6 @@ func (a *App) allocRate(totalAlloc uint64, fps float64) float64 {
return deltaAllocPerFrame * fps / bytesToMegabyte
}
func (a *App) dumpHeap([]string) error {
if _, err := os.Stat("./pprof/"); os.IsNotExist(err) {
if err := os.Mkdir("./pprof/", 0750); err != nil {
a.Fatal(err.Error())
}
}
fileOut, err := os.Create("./pprof/heap.pprof")
if err != nil {
a.Error(err.Error())
}
if err := pprof.WriteHeapProfile(fileOut); err != nil {
a.Fatal(err.Error())
}
if err := fileOut.Close(); err != nil {
a.Fatal(err.Error())
}
return nil
}
func (a *App) evalJS(args []string) error {
val, err := a.scriptEngine.Eval(args[0])
if err != nil {
a.terminal.Errorf(err.Error())
return nil
}
a.Info("%s" + val)
return nil
}
func (a *App) toggleFullScreen([]string) error {
fullscreen := !a.renderer.IsFullScreen()
a.renderer.SetFullScreen(fullscreen)
a.terminal.Infof("fullscreen is now: %v", fullscreen)
return nil
}
func (a *App) setupCaptureFrame(args []string) error {
a.captureState = captureStateFrame
a.capturePath = args[0]
a.captureFrames = nil
return nil
}
func (a *App) doCaptureFrame(target d2interface.Surface) error {
fp, err := os.Create(a.capturePath)
if err != nil {
@ -769,58 +530,6 @@ func (a *App) convertFramesToGif() error {
return nil
}
func (a *App) startAnimationCapture(args []string) error {
a.captureState = captureStateGif
a.capturePath = args[0]
a.captureFrames = nil
return nil
}
func (a *App) stopAnimationCapture([]string) error {
a.captureState = captureStateNone
return nil
}
func (a *App) toggleVsync([]string) error {
vsync := !a.renderer.GetVSyncEnabled()
a.renderer.SetVSyncEnabled(vsync)
a.terminal.Infof("vsync is now: %v", vsync)
return nil
}
func (a *App) toggleFpsCounter([]string) error {
a.showFPS = !a.showFPS
a.terminal.Infof("fps counter is now: %v", a.showFPS)
return nil
}
func (a *App) setTimeScale(args []string) error {
timeScale, err := strconv.ParseFloat(args[0], 64)
if err != nil || timeScale <= 0 {
a.terminal.Errorf("invalid time scale value")
return nil
}
a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale)
a.timeScale = timeScale
return nil
}
func (a *App) quitGame([]string) error {
os.Exit(0)
return nil
}
func (a *App) enterGuiPlayground([]string) error {
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset))
return nil
}
func createZeroedRing(n int) *ring.Ring {
r := ring.New(n)
for i := 0; i < n; i++ {

139
d2app/console_commands.go Normal file
View File

@ -0,0 +1,139 @@
package d2app
import (
"os"
"runtime/pprof"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen"
)
func (a *App) initTerminalCommands() {
terminalCommands := []struct {
name string
desc string
args []string
fn func(args []string) error
}{
{"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap},
{"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen},
{"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame},
{"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture},
{"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture},
{"vsync", "toggles vsync", nil, a.toggleVsync},
{"fps", "toggle fps counter", nil, a.toggleFpsCounter},
{"timescale", "set scalar for elapsed time", []string{"float"}, a.setTimeScale},
{"quit", "exits the game", nil, a.quitGame},
{"screen-gui", "enters the gui playground screen", nil, a.enterGuiPlayground},
{"js", "eval JS scripts", []string{"code"}, a.evalJS},
}
for _, cmd := range terminalCommands {
if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil {
a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error())
}
}
}
func (a *App) dumpHeap([]string) error {
if _, err := os.Stat("./pprof/"); os.IsNotExist(err) {
if err := os.Mkdir("./pprof/", 0750); err != nil {
a.Fatal(err.Error())
}
}
fileOut, err := os.Create("./pprof/heap.pprof")
if err != nil {
a.Error(err.Error())
}
if err := pprof.WriteHeapProfile(fileOut); err != nil {
a.Fatal(err.Error())
}
if err := fileOut.Close(); err != nil {
a.Fatal(err.Error())
}
return nil
}
func (a *App) evalJS(args []string) error {
val, err := a.scriptEngine.Eval(args[0])
if err != nil {
a.terminal.Errorf(err.Error())
return nil
}
a.Info("%s" + val)
return nil
}
func (a *App) toggleFullScreen([]string) error {
fullscreen := !a.renderer.IsFullScreen()
a.renderer.SetFullScreen(fullscreen)
a.terminal.Infof("fullscreen is now: %v", fullscreen)
return nil
}
func (a *App) setupCaptureFrame(args []string) error {
a.captureState = captureStateFrame
a.capturePath = args[0]
a.captureFrames = nil
return nil
}
func (a *App) startAnimationCapture(args []string) error {
a.captureState = captureStateGif
a.capturePath = args[0]
a.captureFrames = nil
return nil
}
func (a *App) stopAnimationCapture([]string) error {
a.captureState = captureStateNone
return nil
}
func (a *App) toggleVsync([]string) error {
vsync := !a.renderer.GetVSyncEnabled()
a.renderer.SetVSyncEnabled(vsync)
a.terminal.Infof("vsync is now: %v", vsync)
return nil
}
func (a *App) toggleFpsCounter([]string) error {
a.showFPS = !a.showFPS
a.terminal.Infof("fps counter is now: %v", a.showFPS)
return nil
}
func (a *App) setTimeScale(args []string) error {
timeScale, err := strconv.ParseFloat(args[0], 64)
if err != nil || timeScale <= 0 {
a.terminal.Errorf("invalid time scale value")
return nil
}
a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale)
a.timeScale = timeScale
return nil
}
func (a *App) quitGame([]string) error {
os.Exit(0)
return nil
}
func (a *App) enterGuiPlayground([]string) error {
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset))
return nil
}

177
d2app/initialization.go Normal file
View File

@ -0,0 +1,177 @@
package d2app
import (
"fmt"
"path/filepath"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
)
func (a *App) initialize() error {
if err := a.initConfig(a.config); err != nil {
return err
}
a.initLanguage()
if err := a.initDataDictionaries(); err != nil {
return err
}
a.timeScale = 1.0
a.lastTime = d2util.Now()
a.lastScreenAdvance = a.lastTime
a.renderer.SetWindowIcon("d2logo.png")
a.terminal.BindLogger()
a.initTerminalCommands()
gui, err := d2gui.CreateGuiManager(a.asset, *a.Options.LogLevel, a.inputManager)
if err != nil {
return err
}
a.guiManager = gui
a.screen = d2screen.NewScreenManager(a.ui, *a.Options.LogLevel, a.guiManager)
a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume)
if err := a.loadStrings(); err != nil {
return err
}
a.ui.Initialize()
return nil
}
const (
fmtErrSourceNotFound = `file not found: %s
Please check your config file at %s
Also, verify that the MPQ files exist at %s
Capitalization in the file name matters.
`
)
func (a *App) initConfig(config *d2config.Configuration) error {
a.config = config
for _, mpqName := range a.config.MpqLoadOrder {
cleanDir := filepath.Clean(a.config.MpqPath)
srcPath := filepath.Join(cleanDir, mpqName)
err := a.asset.AddSource(srcPath, types.AssetSourceMPQ)
if err != nil {
// nolint:stylecheck // we want a multiline error message here..
return fmt.Errorf(fmtErrSourceNotFound, srcPath, a.config.Path(), a.config.MpqPath)
}
}
return nil
}
func (a *App) initLanguage() {
a.language = a.asset.LoadLanguage(d2resource.LocalLanguage)
a.asset.Loader.SetLanguage(&a.language)
a.charset = d2resource.GetFontCharset(a.language)
a.asset.Loader.SetCharset(&a.charset)
}
func (a *App) initDataDictionaries() error {
dictPaths := []string{
d2resource.LevelType, d2resource.LevelPreset, d2resource.LevelWarp,
d2resource.ObjectType, d2resource.ObjectDetails, d2resource.Weapons,
d2resource.Armor, d2resource.Misc, d2resource.Books, d2resource.ItemTypes,
d2resource.UniqueItems, d2resource.Missiles, d2resource.SoundSettings,
d2resource.MonStats, d2resource.MonStats2, d2resource.MonPreset,
d2resource.MonProp, d2resource.MonType, d2resource.MonMode,
d2resource.MagicPrefix, d2resource.MagicSuffix, d2resource.ItemStatCost,
d2resource.ItemRatio, d2resource.StorePage, d2resource.Overlays,
d2resource.CharStats, d2resource.Hireling, d2resource.Experience,
d2resource.Gems, d2resource.QualityItems, d2resource.Runes,
d2resource.DifficultyLevels, d2resource.AutoMap, d2resource.LevelDetails,
d2resource.LevelMaze, d2resource.LevelSubstitutions, d2resource.CubeRecipes,
d2resource.SuperUniques, d2resource.Inventory, d2resource.Skills,
d2resource.SkillCalc, d2resource.MissileCalc, d2resource.Properties,
d2resource.SkillDesc, d2resource.BodyLocations, d2resource.Sets,
d2resource.SetItems, d2resource.AutoMagic, d2resource.TreasureClass,
d2resource.TreasureClassEx, d2resource.States, d2resource.SoundEnvirons,
d2resource.Shrines, d2resource.ElemType, d2resource.PlrMode,
d2resource.PetType, d2resource.NPC, d2resource.MonsterUniqueModifier,
d2resource.MonsterEquipment, d2resource.UniqueAppellation, d2resource.MonsterLevel,
d2resource.MonsterSound, d2resource.MonsterSequence, d2resource.PlayerClass,
d2resource.MonsterPlacement, d2resource.ObjectGroup, d2resource.CompCode,
d2resource.MonsterAI, d2resource.RarePrefix, d2resource.RareSuffix,
d2resource.Events, d2resource.Colors, d2resource.ArmorType,
d2resource.WeaponClass, d2resource.PlayerType, d2resource.Composite,
d2resource.HitClass, d2resource.UniquePrefix, d2resource.UniqueSuffix,
d2resource.CubeModifier, d2resource.CubeType, d2resource.HirelingDescription,
d2resource.LowQualityItems,
}
a.Info("Initializing asset manager")
for _, path := range dictPaths {
err := a.asset.LoadRecords(path)
if err != nil {
return err
}
}
err := a.initAnimationData(d2resource.AnimationData)
if err != nil {
return err
}
return nil
}
const (
fmtLoadAnimData = "loading animation data from: %s"
)
func (a *App) initAnimationData(path string) error {
animDataBytes, err := a.asset.LoadFile(path)
if err != nil {
return err
}
a.Debugf(fmtLoadAnimData, path)
animData := d2data.LoadAnimationData(animDataBytes)
a.Infof("Loaded %d animation data records", len(animData))
a.asset.Records.Animation.Data = animData
return nil
}
func (a *App) loadStrings() error {
tablePaths := []string{
d2resource.PatchStringTable,
d2resource.ExpansionStringTable,
d2resource.StringTable,
}
for _, tablePath := range tablePaths {
_, err := a.asset.LoadStringTable(tablePath)
if err != nil {
return err
}
}
return nil
}

View File

@ -90,7 +90,7 @@ func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, er
toRead := count
for toRead > 0 {
if read, err = v.readInternal(buffer, offset, toRead); err != nil {
return 0, err
return readTotal, err
}
if read == 0 {
@ -128,7 +128,7 @@ func (v *Stream) readInternal(buffer []byte, offset, count uint32) (uint32, erro
func (v *Stream) copy(buffer []byte, offset, pos, count uint32) (uint32, error) {
bytesToCopy := d2math.Min(uint32(len(v.Data))-pos, count)
if bytesToCopy <= 0 {
return 0, nil
return 0, io.EOF
}
copy(buffer[offset:offset+bytesToCopy], v.Data[pos:pos+bytesToCopy])

View File

@ -2,14 +2,13 @@ package asset
import (
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"io"
)
// Source is an abstraction for something that can load and list assets
type Source interface {
fmt.Stringer
Type() types.SourceType
Open(name string) (Asset, error)
Open(name string) (io.ReadSeeker, error)
Path() string
Exists(subPath string) bool
}

View File

@ -0,0 +1,12 @@
package filesystem
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
)
// OnAddSource is a shim method to allow loading of filesystem sources
func OnAddSource(path string) (asset.Source, error) {
return &Source{
Root: path,
}, nil
}

View File

@ -1,6 +1,7 @@
package filesystem
import (
"io"
"os"
"path/filepath"
@ -22,21 +23,14 @@ func (s *Source) Type() types.SourceType {
}
// Open opens a file with the given sub-path within the Root dir of the file system source
func (s *Source) Open(subPath string) (asset.Asset, error) {
file, err := os.Open(s.fullPath(subPath))
func (s *Source) Open(subPath string) (io.ReadSeeker, error) {
return os.Open(s.fullPath(subPath))
}
if err == nil {
a := &Asset{
assetType: types.Ext2AssetType(filepath.Ext(subPath)),
source: s,
path: subPath,
file: file,
}
return a, nil
}
return nil, err
// Exists returns true if the file exists
func (s *Source) Exists(subPath string) bool {
_, err := os.Stat(s.fullPath(subPath))
return os.IsExist(err)
}
func (s *Source) fullPath(subPath string) string {

View File

@ -2,24 +2,25 @@ package d2loader
import (
"fmt"
"os"
"io"
"path/filepath"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
const (
defaultCacheBudget = 1024 * 1024 * 512
defaultCacheEntryWeight = 1
errFmtFileNotFound = "file not found: %s"
defaultCacheBudget = 1024 * 1024 * 512
errFmtFileNotFound = "file not found: %s"
)
const (
@ -33,7 +34,12 @@ const (
// NewLoader creates a new loader
func NewLoader(l d2util.LogLevel) (*Loader, error) {
loader := &Loader{}
loader := &Loader{
LoaderProviders: make(map[types.SourceType]func(path string) (asset.Source, error), 2),
}
loader.LoaderProviders[types.AssetSourceMPQ] = mpq.NewSource
loader.LoaderProviders[types.AssetSourceFileSystem] = filesystem.OnAddSource
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
loader.Logger = d2util.NewLogger()
@ -51,7 +57,8 @@ type Loader struct {
charset *string
d2interface.Cache
*d2util.Logger
Sources []asset.Source
LoaderProviders map[types.SourceType]func(path string) (asset.Source, error)
Sources []asset.Source
}
// SetLanguage sets the language for loader
@ -66,7 +73,7 @@ func (l *Loader) SetCharset(charset *string) {
// Load attempts to load an asset with the given sub-path. The sub-path is relative to the root
// of each asset source root (regardless of the type of asset source)
func (l *Loader) Load(subPath string) (asset.Asset, error) {
func (l *Loader) Load(subPath string) (io.ReadSeeker, error) {
subPath = filepath.Clean(subPath)
if l.language != nil {
@ -77,16 +84,6 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
subPath = strings.ReplaceAll(subPath, tableToken, *language)
}
// first, we check the cache for an existing entry
if cached, found := l.Retrieve(subPath); found {
l.Debug(fmt.Sprintf("Retrieved `%s` from cache", subPath))
a := cached.(asset.Asset)
_, err := a.Seek(0, 0)
return a, err
}
// if it isn't in the cache, we check if each source can open the file
for idx := range l.Sources {
source := l.Sources[idx]
@ -99,9 +96,9 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
}
srcBase, _ := filepath.Abs(source.Path())
l.Info(fmt.Sprintf("from %s, loading %s", srcBase, subPath))
l.Info(fmt.Sprintf("Loaded %s -> %s", srcBase, subPath))
return loadedAsset, l.Insert(subPath, loadedAsset, defaultCacheEntryWeight)
return loadedAsset, nil
}
return nil, fmt.Errorf(errFmtFileNotFound, subPath)
@ -111,52 +108,45 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
// or a file on the host filesystem. In the case that it is a file, the file extension is used
// to determine the type of asset source. In the case that the path points to a directory, a
// FileSystemSource will be added.
func (l *Loader) AddSource(path string) (asset.Source, error) {
func (l *Loader) AddSource(path string, sourceType types.SourceType) error {
if l.Sources == nil {
l.Sources = make([]asset.Source, 0)
}
cleanPath := filepath.Clean(path)
info, err := os.Lstat(cleanPath)
source, err := l.LoaderProviders[sourceType](cleanPath)
if err != nil {
l.Error(err.Error())
return nil, err
return err
}
mode := info.Mode()
l.Infof("Adding source: '%s'", cleanPath)
l.Sources = append(l.Sources, source)
sourceType := types.AssetSourceUnknown
if mode.IsDir() {
sourceType = types.AssetSourceFileSystem
}
if mode.IsRegular() {
sourceType = types.CheckSourceType(cleanPath)
}
switch sourceType {
case types.AssetSourceMPQ:
source, err := mpq.NewSource(cleanPath)
if err == nil {
l.Info(fmt.Sprintf("adding MPQ source `%s`", cleanPath))
l.Sources = append(l.Sources, source)
return source, nil
}
case types.AssetSourceFileSystem:
source := &filesystem.Source{
Root: cleanPath,
}
l.Info(fmt.Sprintf("adding filesystem source `%s`", cleanPath))
l.Sources = append(l.Sources, source)
return source, nil
case types.AssetSourceUnknown:
l.Warning(fmt.Sprintf("unknown asset source `%s`", cleanPath))
}
return nil, fmt.Errorf("unknown asset source `%s`", cleanPath)
return nil
}
func (l *Loader) Exists(subPath string) bool {
subPath = filepath.Clean(subPath)
if l.language != nil {
charset := l.charset
language := l.language
subPath = strings.ReplaceAll(subPath, fontToken, *charset)
subPath = strings.ReplaceAll(subPath, tableToken, *language)
}
// if it isn't in the cache, we check if each source can open the file
for idx := range l.Sources {
source := l.Sources[idx]
// if the source can open the file, then we cache it and return it
if source.Exists(subPath) {
return true
}
}
return false
}

View File

@ -2,12 +2,13 @@ package d2loader
import (
"fmt"
"io"
"log"
"testing"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
const (
@ -36,11 +37,11 @@ func TestLoader_NewLoader(t *testing.T) {
func TestLoader_AddSource(t *testing.T) {
loader, _ := NewLoader(d2util.LogLevelDefault)
sourceA, errA := loader.AddSource(sourcePathA)
sourceB, errB := loader.AddSource(sourcePathB)
sourceC, errC := loader.AddSource(sourcePathC)
sourceD, errD := loader.AddSource(sourcePathD)
sourceE, errE := loader.AddSource(badSourcePath)
errA := loader.AddSource(sourcePathA, types.AssetSourceFileSystem)
errB := loader.AddSource(sourcePathB, types.AssetSourceFileSystem)
errC := loader.AddSource(sourcePathC, types.AssetSourceFileSystem)
errD := loader.AddSource(sourcePathD, types.AssetSourceFileSystem)
errE := loader.AddSource(badSourcePath, types.AssetSourceMPQ)
if errA != nil {
t.Error(errA)
@ -62,25 +63,6 @@ func TestLoader_AddSource(t *testing.T) {
t.Error("expecting error on bad file path")
}
if sourceA.String() != sourcePathA {
t.Error("source path not the same as what we added")
}
if sourceB.String() != sourcePathB {
t.Error("source path not the same as what we added")
}
if sourceC.String() != sourcePathC {
t.Error("source path not the same as what we added")
}
if sourceD.String() != sourcePathD {
t.Error("source path not the same as what we added")
}
if sourceE != nil {
t.Error("source for bad path should be nil")
}
}
// nolint:gocyclo // this is just a test, not a big deal if we ignore linter here
@ -88,25 +70,25 @@ func TestLoader_Load(t *testing.T) {
loader, _ := NewLoader(d2util.LogLevelDefault)
// we expect files common to any source to come from here
commonSource, err := loader.AddSource(sourcePathB)
err := loader.AddSource(sourcePathB, types.AssetSourceFileSystem)
if err != nil {
t.Fail()
log.Print(err)
}
_, err = loader.AddSource(sourcePathD)
err = loader.AddSource(sourcePathD, types.AssetSourceMPQ)
if err != nil {
t.Fail()
log.Print(err)
}
_, err = loader.AddSource(sourcePathA)
err = loader.AddSource(sourcePathA, types.AssetSourceFileSystem)
if err != nil {
t.Fail()
log.Print(err)
}
_, err = loader.AddSource(sourcePathC)
err = loader.AddSource(sourcePathC, types.AssetSourceFileSystem)
if err != nil {
t.Fail()
log.Print(err)
@ -124,8 +106,6 @@ func TestLoader_Load(t *testing.T) {
if entryCommon == nil || errCommon != nil {
t.Error("common entry should exist")
} else if entryCommon.Source() != commonSource {
t.Error("common entry should come from the first loader source")
}
if errA != nil || errB != nil || errC != nil || errD != nil {
@ -145,7 +125,7 @@ func TestLoader_Load(t *testing.T) {
buffer := make([]byte, 1)
tests := []struct {
entry asset.Asset
entry io.ReadSeeker
data string
}{
{entryCommon, "b"}, // sourcePathB is loaded first, we expect a "b"
@ -172,8 +152,8 @@ func TestLoader_Load(t *testing.T) {
got := string(result[0])
if got != expected {
fmtStr := "unexpected data in file %s, loaded from source `%s`: expected `%s`, got `%s`"
msg := fmt.Sprintf(fmtStr, entry.Path(), entry.Source(), expected, got)
fmtStr := "unexpected data in file, expected %q, got %q"
msg := fmt.Sprintf(fmtStr, expected, got)
t.Error(msg)
}
}

View File

@ -2,7 +2,6 @@ package mpq
import (
"fmt"
"io"
"path/filepath"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -42,12 +41,13 @@ func (a *Asset) Path() string {
// Read will read asset data into the given buffer
func (a *Asset) Read(buf []byte) (n int, err error) {
totalRead, err := a.stream.Read(buf)
if totalRead == 0 {
return 0, io.EOF
}
return totalRead, err
return a.stream.Read(buf)
//totalRead, err := a.stream.Read(buf)
//if totalRead == 0 {
// return 0, io.EOF
//}
//
//return totalRead, err
}
// Seek will seek the read position for the next read operation

View File

@ -1,12 +1,12 @@
package mpq
import (
"io"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
)
// static check that Source implements AssetSource
@ -27,27 +27,16 @@ type Source struct {
MPQ d2interface.Archive
}
// Type returns the asset type, for MPQ's it always returns the MPQ asset source type
func (v *Source) Type() types.SourceType {
return types.AssetSourceMPQ
// Open attempts to open a file within the MPQ archive
func (v *Source) Open(name string) (a io.ReadSeeker, err error) {
name = cleanName(name)
return v.MPQ.ReadFileStream(name)
}
// Open attempts to open a file within the MPQ archive
func (v *Source) Open(name string) (a asset.Asset, err error) {
name = cleanName(name)
stream, err := v.MPQ.ReadFileStream(name)
if err != nil {
return nil, err
}
a = &Asset{
source: v,
stream: stream,
path: name,
}
return a, nil
// Exists returns true if the file exists
func (v *Source) Exists(subPath string) bool {
subPath = cleanName(subPath)
return v.MPQ.Contains(subPath)
}
// Path returns the path of the MPQ on the host filesystem

View File

@ -3,8 +3,17 @@ package d2asset
import (
"fmt"
"image/color"
"io"
"io/ioutil"
"path/filepath"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
@ -20,7 +29,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
)
@ -33,6 +41,10 @@ const (
fontBudget = 128
paletteBudget = 64
paletteTransformBudget = 64
dt1Budget = 4096 * 2048 * 128
ds1Budget = 4096 * 2048 * 128
cofBudget = 4096 * 2048 * 128
dccBudget = 4096 * 2048 * 128
)
const (
@ -53,6 +65,10 @@ type AssetManager struct {
*d2util.Logger
*d2loader.Loader
tables []d2tbl.TextDictionary
dt1s d2interface.Cache
ds1s d2interface.Cache
cofs d2interface.Cache
dccs d2interface.Cache
animations d2interface.Cache
fonts d2interface.Cache
palettes d2interface.Cache
@ -69,7 +85,7 @@ func (am *AssetManager) SetLogLevel(level d2util.LogLevel) {
}
// LoadAsset loads an asset
func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
func (am *AssetManager) LoadAsset(filePath string) (io.ReadSeeker, error) {
data, err := am.Loader.Load(filePath)
if err != nil {
errStr := fmt.Sprintf(fmtLoadAsset, filePath, err.Error())
@ -81,19 +97,19 @@ func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
}
// LoadFileStream streams an MPQ file from a source file path
func (am *AssetManager) LoadFileStream(filePath string) (d2interface.DataStream, error) {
func (am *AssetManager) LoadFileStream(filePath string) (io.ReadSeeker, error) {
am.Logger.Debugf("Loading FileStream: %s", filePath)
return am.LoadAsset(filePath)
}
// LoadFile loads an entire file from a source file path as a []byte
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) { // I DO NOT LIKE THIS! - Essial
fileAsset, err := am.LoadAsset(filePath)
if err != nil {
return nil, err
}
data, err := fileAsset.Data()
data, err := ioutil.ReadAll(fileAsset)
if err != nil {
return nil, err
}
@ -103,13 +119,11 @@ func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
// FileExists checks if a file exists on the underlying file system at the given file path.
func (am *AssetManager) FileExists(filePath string) (bool, error) {
filePath = filepath.Clean(filePath)
am.Logger.Debugf("Checking if file exists %s", filePath)
if loadedAsset, err := am.Loader.Load(filePath); err != nil || loadedAsset == nil {
return false, err
}
return true, nil
return am.Loader.Exists(filePath), nil
}
// LoadLanguage loads language from resource path
@ -147,11 +161,6 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
am.Debugf(fmtLoadAnimation, animationPath, palettePath, effect)
animAsset, err := am.LoadAsset(animationPath)
if err != nil {
return nil, err
}
palette, err := am.LoadPalette(palettePath)
if err != nil {
return nil, err
@ -159,7 +168,7 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
var animation d2interface.Animation
switch animAsset.Type() {
switch types.Ext2AssetType(filepath.Ext(animationPath)) {
case types.AssetTypeDC6:
animation, err = am.loadDC6(animationPath, palette, effect)
if err != nil {
@ -171,7 +180,7 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
return nil, err
}
default:
return nil, fmt.Errorf("unknown Animation format for file: %s", animAsset.Path())
return nil, fmt.Errorf("unknown Animation format for file: %s", animationPath)
}
err = am.animations.Insert(cachePath, animation, defaultCacheEntryWeight)
@ -237,12 +246,7 @@ func (am *AssetManager) LoadPalette(palettePath string) (d2interface.Palette, er
return cached.(d2interface.Palette), nil
}
paletteAsset, err := am.LoadAsset(palettePath)
if err != nil {
return nil, err
}
if paletteAsset.Type() != types.AssetTypePalette {
if types.Ext2AssetType(filepath.Ext(palettePath)) != types.AssetTypePalette {
return nil, fmt.Errorf("not an instance of a palette: %s", palettePath)
}
@ -390,12 +394,7 @@ func (am *AssetManager) loadDC6(path string,
// loadDCC creates an Animation from d2dcc.DCC and d2dat.DATPalette
func (am *AssetManager) loadDCC(path string,
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
dccData, err := am.LoadFile(path)
if err != nil {
return nil, err
}
dcc, err := d2dcc.Load(dccData)
dcc, err := am.LoadDCC(path)
if err != nil {
return nil, err
}
@ -448,6 +447,10 @@ func (am *AssetManager) commandAssetSpam(term d2interface.Terminal) func([]strin
am.fonts.SetVerbose(verbose)
am.transforms.SetVerbose(verbose)
am.animations.SetVerbose(verbose)
am.dt1s.SetVerbose(verbose)
am.ds1s.SetVerbose(verbose)
am.dccs.SetVerbose(verbose)
am.cofs.SetVerbose(verbose)
return nil
}
@ -474,6 +477,99 @@ func (am *AssetManager) commandAssetClear([]string) error {
am.transforms.Clear()
am.animations.Clear()
am.fonts.Clear()
am.dt1s.Clear()
am.ds1s.Clear()
am.dccs.Clear()
am.cofs.Clear()
return nil
}
func (am *AssetManager) LoadDT1(dt1Path string) (*d2dt1.DT1, error) {
if dt1Value, found := am.dt1s.Retrieve(dt1Path); found {
return dt1Value.(*d2dt1.DT1), nil
}
fileData, err := am.LoadFile("/data/global/tiles/" + dt1Path)
if err != nil {
return nil, fmt.Errorf("Could not load /data/global/tiles/%s", dt1Path)
}
dt1, err := d2dt1.LoadDT1(fileData)
if err != nil {
return nil, err
}
if err := am.dt1s.Insert(dt1Path, dt1, defaultCacheEntryWeight); err != nil {
return nil, err
}
return dt1, nil
}
func (am *AssetManager) LoadDS1(ds1Path string) (*d2ds1.DS1, error) {
if ds1Value, found := am.dt1s.Retrieve(ds1Path); found {
return ds1Value.(*d2ds1.DS1), nil
}
fileData, err := am.LoadFile("/data/global/tiles/" + ds1Path)
if err != nil {
return nil, err
}
ds1, err := d2ds1.LoadDS1(fileData)
if err != nil {
return nil, err
}
if err := am.dt1s.Insert(ds1Path, ds1, defaultCacheEntryWeight); err != nil {
return nil, err
}
return ds1, nil
}
func (am *AssetManager) LoadCOF(cofPath string) (*d2cof.COF, error) {
if cofValue, found := am.cofs.Retrieve(cofPath); found {
return cofValue.(*d2cof.COF), nil
}
fileData, err := am.LoadFile(cofPath)
if err != nil {
return nil, err
}
cof, err := d2cof.Load(fileData)
if err != nil {
return nil, err
}
if err := am.cofs.Insert(cofPath, cof, defaultCacheEntryWeight); err != nil {
return nil, err
}
return cof, nil
}
func (am *AssetManager) LoadDCC(dccPath string) (*d2dcc.DCC, error) {
if dccValue, found := am.dccs.Retrieve(dccPath); found {
return dccValue.(*d2dcc.DCC), nil
}
fileData, err := am.LoadFile(dccPath)
if err != nil {
return nil, err
}
dcc, err := d2dcc.Load(fileData)
if err != nil {
return nil, err
}
if err := am.dccs.Insert(dccPath, dcc, defaultCacheEntryWeight); err != nil {
return nil, err
}
return dcc, nil
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strings"
"sync"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
@ -161,15 +162,26 @@ func (c *Composite) SetDirection(direction int) {
return
}
wg := sync.WaitGroup{}
c.direction = direction
wg.Add(len(c.mode.layers))
for layerIdx := range c.mode.layers {
layer := c.mode.layers[layerIdx]
if layer != nil {
if err := layer.SetDirection(c.direction); err != nil {
fmt.Printf("failed to set direction of layer: %d, err: %v\n", layerIdx, err)
go func(idx int) {
defer wg.Done()
layer := c.mode.layers[idx]
if layer != nil {
if err := layer.SetDirection(c.direction); err != nil {
fmt.Printf("failed to set direction of layer: %d, err: %v\n", idx, err)
}
}
}
}(layerIdx)
}
wg.Wait()
}
// GetDirection returns the current direction the composite is facing
@ -248,7 +260,7 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
return nil, fmt.Errorf("composite not found at path '%s': %v", cofPath, err)
}
cof, err := c.loadCOF(cofPath)
cof, err := c.LoadCOF(cofPath)
if err != nil {
return nil, err
}
@ -306,13 +318,13 @@ func (c *Composite) loadCompositeLayer(layerKey, layerValue, animationMode, weap
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dc6", c.basePath, c.token, layerKey, c.token, layerKey, layerValue, animationMode, weaponClass),
}
for _, animationPath := range animationPaths {
exists, err := c.FileExists(animationPath)
for idx := range animationPaths {
exists, err := c.FileExists(animationPaths[idx])
if !exists || err != nil {
return nil, fmt.Errorf("animation path '%s' not found: %v", animationPath, err)
return nil, fmt.Errorf("animation path '%s' not found: %v", animationPaths[idx], err)
}
animation, err := c.LoadAnimationWithEffect(animationPath, palettePath, drawEffect)
animation, err := c.LoadAnimationWithEffect(animationPaths[idx], palettePath, drawEffect)
if err == nil {
return animation, nil
}
@ -359,15 +371,6 @@ func (c *Composite) updateSize() {
c.size.Height = biggestH
}
func (c *Composite) loadCOF(cofPath string) (*d2cof.COF, error) {
cofData, err := c.LoadFile(cofPath)
if err != nil {
return nil, err
}
return d2cof.Load(cofData)
}
func baseString(baseType d2enum.ObjectType) string {
switch baseType {
case d2enum.ObjectTypePlayer:

View File

@ -32,6 +32,10 @@ func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) {
fonts: d2cache.CreateCache(fontBudget),
palettes: d2cache.CreateCache(paletteBudget),
transforms: d2cache.CreateCache(paletteTransformBudget),
dt1s: d2cache.CreateCache(dt1Budget),
ds1s: d2cache.CreateCache(ds1Budget),
cofs: d2cache.CreateCache(cofBudget),
dccs: d2cache.CreateCache(dccBudget),
Records: records,
}

View File

@ -137,6 +137,10 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context,
soundFile += sfx
}
if fileExists, _ := eap.asset.FileExists(soundFile); !fileExists {
soundFile = "data/global/music/" + sfx
}
audioData, err := eap.asset.LoadFileStream(soundFile)
if err != nil {
@ -153,6 +157,10 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context,
eap.Fatal(err.Error())
}
if d == nil {
eap.Fatal("Decoded data is nil")
}
var player *audio.Player
if loop {

View File

@ -3,13 +3,15 @@ package ebiten
import (
"io"
"math"
"sync"
"github.com/hajimehoshi/ebiten/v2/audio"
)
type panStream struct {
io.ReadSeeker
pan float64 // -1: left; 0: center; 1: right
pan float64 // -1: left; 0: center; 1: right
Lock sync.Mutex
}
const (
@ -24,6 +26,9 @@ func newPanStreamFromReader(src io.ReadSeeker) *panStream {
}
func (s *panStream) Read(p []byte) (n int, err error) {
s.Lock.Lock()
defer s.Lock.Unlock()
n, err = s.ReadSeeker.Read(p)
if err != nil {
return
@ -54,7 +59,9 @@ type SoundEffect struct {
// SetPan sets the audio pan, left is -1.0, center is 0.0, right is 1.0
func (v *SoundEffect) SetPan(pan float64) {
v.panStream.Lock.Lock()
v.panStream.pan = pan
v.panStream.Lock.Unlock()
}
// SetVolume ets the volume

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -97,13 +96,7 @@ func (m *MapEngine) addDT1(fileName string) {
}
}
fileData, err := m.asset.LoadFile("/data/global/tiles/" + fileName)
if err != nil {
m.Fatalf("Could not load /data/global/tiles/%s", fileName)
return
}
dt1, err := d2dt1.LoadDT1(fileData)
dt1, err := m.asset.LoadDT1(fileName)
if err != nil {
m.Error(err.Error())
}
@ -120,12 +113,7 @@ func (m *MapEngine) AddDS1(fileName string) {
return
}
fileData, err := m.asset.LoadFile("/data/global/tiles/" + fileName)
if err != nil {
panic(err)
}
ds1, err := d2ds1.LoadDS1(fileData)
ds1, err := m.asset.LoadDS1(fileName)
if err != nil {
m.Error(err.Error())
}

View File

@ -88,11 +88,11 @@ func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Rendere
result.Camera.position = &startPosition
result.viewport.SetCamera(&result.Camera)
if err := term.Bind("mapdebugvis", "set map debug visualization level", nil, result.commandMapDebugVis); err != nil {
if err := term.Bind("mapdebugvis", "set map debug visualization level", []string{"level"}, result.commandMapDebugVis); err != nil {
result.Errorf("could not bind the mapdebugvis action, err: %v", err)
}
if err := term.Bind("entitydebugvis", "set entity debug visualization level", nil, result.commandEntityDebugVis); err != nil {
if err := term.Bind("entitydebugvis", "set entity debug visualization level", []string{"level"}, result.commandEntityDebugVis); err != nil {
result.Errorf("could not bind the entitydebugvis action, err: %v", err)
}
@ -109,6 +109,10 @@ func (mr *MapRenderer) UnbindTerminalCommands(term d2interface.Terminal) error {
}
func (mr *MapRenderer) commandMapDebugVis(args []string) error {
if len(args) < 1 {
return fmt.Errorf("invalid argument supplied")
}
level, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid argument supplied")

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
@ -54,12 +53,7 @@ func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fil
continue
}
fileData, err := f.asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
if err != nil {
panic(err)
}
dt1, err := d2dt1.LoadDT1(fileData)
dt1, err := f.asset.LoadDT1(levelTypeDt1)
if err != nil {
f.Error(err.Error())
return nil

94
d2thread/mainthread.go Normal file
View File

@ -0,0 +1,94 @@
// Package d2thread is a package graciously taken from https://github.com/faiface/mainthread
package d2thread
import (
"errors"
"runtime"
)
// CallQueueCap is the capacity of the call queue. This means how many calls to CallNonBlock will not
// block until some call finishes.
//
// The default value is 16 and should be good for 99% usecases.
var (
callQueue chan func() //nolint:gochecknoglobals
)
func init() {
runtime.LockOSThread()
}
func checkRun() {
if callQueue == nil {
panic(errors.New("mainthread: did not call Run"))
}
}
// Run enables mainthread package functionality. To use mainthread package, put your main function
// code into the run function (the argument to Run) and simply call Run from the real main function.
//
// Run returns when run (argument) function finishes.
func Run(run func()) {
var CallQueueCap = 16
callQueue = make(chan func(), CallQueueCap)
done := make(chan struct{})
go func() {
run()
done <- struct{}{}
}()
for {
select {
case f := <-callQueue:
f()
case <-done:
return
}
}
}
// CallNonBlock queues function f on the main thread and returns immediately. Does not wait until f
// finishes.
func CallNonBlock(f func()) {
checkRun()
callQueue <- f
}
// Call queues function f on the main thread and blocks until the function f finishes.
func Call(f func()) {
checkRun()
done := make(chan struct{})
callQueue <- func() {
f()
done <- struct{}{}
}
<-done
}
// CallErr queues function f on the main thread and returns an error returned by f.
func CallErr(f func() error) error {
checkRun()
errChan := make(chan error)
callQueue <- func() {
errChan <- f()
}
return <-errChan
}
// CallVal queues function f on the main thread and returns a value returned by f.
func CallVal(f func() interface{}) interface{} {
checkRun()
respChan := make(chan interface{})
callQueue <- func() {
respChan <- f()
}
return <-respChan
}

2
go.sum
View File

@ -4,6 +4,8 @@ github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjs
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928=
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=