diff --git a/d2core/d2components/animation.go b/d2core/d2components/animation.go index 307211d7..0896aafa 100644 --- a/d2core/d2components/animation.go +++ b/d2core/d2components/animation.go @@ -97,4 +97,3 @@ func (cm *AnimationMap) Remove(id akara.EID) { delete(cm.components, id) cm.world.UpdateEntity(id) } - diff --git a/d2core/d2components/doc.go b/d2core/d2components/doc.go new file mode 100644 index 00000000..2bf1756f --- /dev/null +++ b/d2core/d2components/doc.go @@ -0,0 +1,2 @@ +// Package d2components provides all of the ECS components +package d2components diff --git a/d2core/d2systems/app_bootstrap.go b/d2core/d2systems/app_bootstrap.go index a9f6368b..c1e65524 100644 --- a/d2core/d2systems/app_bootstrap.go +++ b/d2core/d2systems/app_bootstrap.go @@ -1,11 +1,12 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" - "github.com/gravestench/akara" "os" "path" + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) @@ -30,7 +31,7 @@ func NewAppBootstrapSystem() *AppBootstrapSystem { d2components.FileType, d2components.FileHandle, d2components.FilePath, - ). + ). Forbid( // files which have been loaded d2components.GameConfig, d2components.StringTable, @@ -44,7 +45,7 @@ func NewAppBootstrapSystem() *AppBootstrapSystem { d2components.Dt1, d2components.Wav, d2components.AnimData, - ). + ). Build() // we are interested in actual game config instances, too @@ -52,7 +53,7 @@ func NewAppBootstrapSystem() *AppBootstrapSystem { sys := &AppBootstrapSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToCheck, gameConfigs), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } sys.SetPrefix(logPrefixAppBootstrap) @@ -75,7 +76,7 @@ type AppBootstrapSystem struct { *d2components.FileSourceMap } -// Init will inject (or use existing) components related to setting up the config sources and +// Init will inject (or use existing) components related to setting up the config sources func (m *AppBootstrapSystem) Init(world *akara.World) { m.Info("initializing ...") @@ -102,7 +103,6 @@ func (m *AppBootstrapSystem) injectSystems() { m.World.AddSystem(NewGameConfigSystem()) m.World.AddSystem(NewAssetLoader()) m.World.AddSystem(NewGameObjectFactory()) - m.World.AddSystem(NewUpdateCounterSystem()) } // we make two entities and assign file paths for the two directories that @@ -136,7 +136,6 @@ func (m *AppBootstrapSystem) setupConfigSources() { } } - func (m *AppBootstrapSystem) setupConfigFile() { // add an entity that will get picked up by the game config system and loaded m.AddFilePath(m.NewEntity()).Path = configFileName @@ -154,6 +153,7 @@ func (m *AppBootstrapSystem) Update() { m.Infof("found %d new configs to parse", len(configs)) firstConfigEntityID := configs[0] + cfg, found := m.GetGameConfig(firstConfigEntityID) if !found { return diff --git a/d2core/d2systems/asset_loader.go b/d2core/d2systems/asset_loader.go index ba2c42d0..fd9788fe 100644 --- a/d2core/d2systems/asset_loader.go +++ b/d2core/d2systems/asset_loader.go @@ -1,15 +1,13 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "io" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache" - + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2animdata" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6" @@ -19,9 +17,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2pl2" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" - "github.com/gravestench/akara" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) @@ -31,7 +28,7 @@ const ( ) const ( - LogPrefixAssetLoader = "Asset Loader System" + logPrefixAssetLoader = "Asset Loader System" ) // NewAssetLoader creates a new asset loader instance @@ -62,24 +59,24 @@ func NewAssetLoader() *AssetLoaderSystem { assetLoader := &AssetLoaderSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToLoad, fileSources), - cache: d2cache.CreateCache(assetCacheBudget).(*d2cache.Cache), - Logger: d2util.NewLogger(), + cache: d2cache.CreateCache(assetCacheBudget).(*d2cache.Cache), + Logger: d2util.NewLogger(), } - assetLoader.SetPrefix(LogPrefixAssetLoader) + assetLoader.SetPrefix(logPrefixAssetLoader) return assetLoader } var _ akara.System = &AssetLoaderSystem{} -// AssetLoaderSystem is responsible for parsing file handle data into various structs, like COF or DC6 +// AssetLoaderSystem is responsible for parsing data from file handles into various structs, like COF or DC6 type AssetLoaderSystem struct { *akara.BaseSubscriberSystem *d2util.Logger - fileSub *akara.Subscription - sourceSub *akara.Subscription - cache *d2cache.Cache + fileSub *akara.Subscription + sourceSub *akara.Subscription + cache *d2cache.Cache *d2components.FilePathMap *d2components.FileTypeMap *d2components.FileHandleMap @@ -99,7 +96,7 @@ type AssetLoaderSystem struct { } // Init injects component maps related to various asset types -func (m *AssetLoaderSystem) Init(world *akara.World) { +func (m *AssetLoaderSystem) Init(_ *akara.World) { m.Info("initializing ...") m.fileSub = m.Subscriptions[0] @@ -213,41 +210,64 @@ func (m *AssetLoaderSystem) assignFromCache(id akara.EID, path string, t d2enum. return found } +//nolint:gocyclo // this big switch statement is unfortunate, but necessary func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.FileType, data []byte) { go func() { switch t { case d2enum.FileTypeStringTable: m.Infof("Loading string table: %s", path) - m.loadStringTable(id, path, data) // TODO: add error handling for string table load + m.loadStringTable(id, path, data) case d2enum.FileTypeFontTable: m.Infof("Loading font table: %s", path) - m.loadFontTable(id, path, data) // TODO: add error handling for string table load + m.loadFontTable(id, path, data) case d2enum.FileTypeDataDictionary: m.Infof("Loading data dictionary: %s", path) - m.loadDataDictionary(id, path, data) // TODO: add error handling for data dict load + m.loadDataDictionary(id, path, data) case d2enum.FileTypePalette: m.Infof("Loading palette: %s", path) - m.loadPalette(id, path, data) + + if err := m.loadPalette(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypePaletteTransform: m.Infof("Loading palette transform: %s", path) - m.loadPaletteTransform(id, path, data) + + if err := m.loadPaletteTransform(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeCOF: m.Infof("Loading COF: %s", path) - m.loadCOF(id, path, data) + + if err := m.loadCOF(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeDC6: m.Infof("Loading DC6: %s", path) - m.loadDC6(id, path, data) + + if err := m.loadDC6(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeDCC: m.Infof("Loading DCC: %s", path) - m.loadDCC(id, path, data) + + if err := m.loadDCC(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeDS1: m.Infof("Loading DS1: %s", path) - m.loadDS1(id, path, data) + + if err := m.loadDS1(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeDT1: m.Infof("Loading DT1: %s", path) - m.loadDT1(id, path, data) + + if err := m.loadDT1(id, path, data); err != nil { + m.Error(err.Error()) + } case d2enum.FileTypeWAV: m.Infof("Loading WAV: %s", path) + fh, found := m.GetFileHandle(id) if !found { return @@ -256,7 +276,10 @@ func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.Fi m.loadWAV(id, path, fh.Data) case d2enum.FileTypeD2: m.Infof("Loading animation data: %s", path) - m.loadAnimData(id, path, data) + + if err := m.loadAnimData(id, path, data); err != nil { + m.Error(err.Error()) + } } }() } @@ -265,25 +288,37 @@ func (m *AssetLoaderSystem) loadStringTable(id akara.EID, path string, data []by txt := d2tbl.LoadTextDictionary(data) loaded := &txt m.AddStringTable(id).TextDictionary = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } func (m *AssetLoaderSystem) loadFontTable(id akara.EID, path string, data []byte) { m.AddFontTable(id).Data = data - m.cache.Insert(path, data, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, data, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } func (m *AssetLoaderSystem) loadDataDictionary(id akara.EID, path string, data []byte) { loaded := d2txt.LoadDataDictionary(data) m.AddDataDictionary(id).DataDictionary = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } func (m *AssetLoaderSystem) loadPalette(id akara.EID, path string, data []byte) error { loaded, err := d2dat.Load(data) if err == nil { m.AddPalette(id).Palette = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -293,7 +328,10 @@ func (m *AssetLoaderSystem) loadPaletteTransform(id akara.EID, path string, data loaded, err := d2pl2.Load(data) if err == nil { m.AddPaletteTransform(id).Transform = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -303,7 +341,10 @@ func (m *AssetLoaderSystem) loadCOF(id akara.EID, path string, data []byte) erro loaded, err := d2cof.Load(data) if err == nil { m.AddCof(id).COF = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -313,7 +354,10 @@ func (m *AssetLoaderSystem) loadDC6(id akara.EID, path string, data []byte) erro loaded, err := d2dc6.Load(data) if err == nil { m.AddDc6(id).DC6 = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -323,7 +367,10 @@ func (m *AssetLoaderSystem) loadDCC(id akara.EID, path string, data []byte) erro loaded, err := d2dcc.Load(data) if err == nil { m.AddDcc(id).DCC = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -333,7 +380,10 @@ func (m *AssetLoaderSystem) loadDS1(id akara.EID, path string, data []byte) erro loaded, err := d2ds1.LoadDS1(data) if err == nil { m.AddDs1(id).DS1 = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -343,7 +393,10 @@ func (m *AssetLoaderSystem) loadDT1(id akara.EID, path string, data []byte) erro loaded, err := d2dt1.LoadDT1(data) if err == nil { m.AddDt1(id).DT1 = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err @@ -352,14 +405,20 @@ func (m *AssetLoaderSystem) loadDT1(id akara.EID, path string, data []byte) erro func (m *AssetLoaderSystem) loadWAV(id akara.EID, path string, seeker io.ReadSeeker) { component := m.AddWav(id) component.Data = seeker - m.cache.Insert(path, seeker, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, seeker, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } func (m *AssetLoaderSystem) loadAnimData(id akara.EID, path string, data []byte) error { loaded, err := d2animdata.Load(data) if err == nil { m.AddAnimData(id).AnimationData = loaded - m.cache.Insert(path, loaded, assetCacheEntryWeight) + + if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil { + m.Error(cacheErr.Error()) + } } return err diff --git a/d2core/d2systems/doc.go b/d2core/d2systems/doc.go new file mode 100644 index 00000000..601c8e09 --- /dev/null +++ b/d2core/d2systems/doc.go @@ -0,0 +1,2 @@ +// Package d2systems provides all of the ECS systems +package d2systems diff --git a/d2core/d2systems/file_handle_resolver.go b/d2core/d2systems/file_handle_resolver.go index 628aaa9a..b440ed58 100644 --- a/d2core/d2systems/file_handle_resolver.go +++ b/d2core/d2systems/file_handle_resolver.go @@ -1,9 +1,10 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache" @@ -16,7 +17,7 @@ import ( ) const ( - languageTokenFont = "{LANG_FONT}" + languageTokenFont = "{LANG_FONT}" // nolint:gosec // no security issue here... languageTokenStringTable = "{LANG}" ) @@ -29,6 +30,7 @@ const ( logPrefixFileHandleResolver = "File Handle Resolver" ) +// NewFileHandleResolver creates a new file handle resolver system func NewFileHandleResolver() *FileHandleResolutionSystem { // this filter is for entities that have a file path and file type but no file handle. filesToSource := akara.NewFilter(). @@ -44,8 +46,8 @@ func NewFileHandleResolver() *FileHandleResolutionSystem { fhr := &FileHandleResolutionSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToSource, sourcesToUse), - cache: d2cache.CreateCache(fileHandleCacheBudget).(*d2cache.Cache), - Logger: d2util.NewLogger(), + cache: d2cache.CreateCache(fileHandleCacheBudget).(*d2cache.Cache), + Logger: d2util.NewLogger(), } fhr.SetPrefix(logPrefixFileHandleResolver) @@ -53,6 +55,13 @@ func NewFileHandleResolver() *FileHandleResolutionSystem { return fhr } +// FileHandleResolutionSystem is responsible for using file sources to resolve files. +// File sources are checked in the order that the sources were added. +// +// A file source can be something like an MPQ archive or a file system directory on the host machine. +// +// A file handle is a primitive representation of a loaded file; something that has data +// in the form of a byte slice, but has not been parsed into a more meaningful struct, like a DC6 animation. type FileHandleResolutionSystem struct { *akara.BaseSubscriberSystem *d2util.Logger @@ -66,7 +75,7 @@ type FileHandleResolutionSystem struct { } // Init initializes the system with the given world -func (m *FileHandleResolutionSystem) Init(world *akara.World) { +func (m *FileHandleResolutionSystem) Init(_ *akara.World) { m.Info("initializing ...") m.filesToLoad = m.Subscriptions[0] @@ -80,7 +89,9 @@ func (m *FileHandleResolutionSystem) Init(world *akara.World) { m.fileSources = m.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) } -// Process processes all of the Entities +// Update iterates over entities which have not had a file handle resolved. +// For each source, it attempts to load this file with the given source. +// If the file can be opened by the source, we create the file handle using that source. func (m *FileHandleResolutionSystem) Update() { filesToLoad := m.filesToLoad.GetEntities() sourcesToUse := m.sourcesToUse.GetEntities() @@ -146,21 +157,21 @@ func (m *FileHandleResolutionSystem) loadFileWithSource(fileID, sourceID akara.E tryPath := strings.ReplaceAll(fp.Path, "sfx", "music") tmpComponent := &d2components.FilePathComponent{Path: tryPath} - cacheKey := m.makeCacheKey(tryPath, sourceFp.Path) + cacheKey = m.makeCacheKey(tryPath, sourceFp.Path) if entry, found := m.cache.Retrieve(cacheKey); found { component := m.fileHandles.AddFileHandle(fileID) component.Data = entry.(d2interface.DataStream) fp.Path = tryPath return true - } else { - data, err = source.Open(tmpComponent) - if err != nil { - return false - } - - fp.Path = tryPath } + + data, err = source.Open(tmpComponent) + if err != nil { + return false + } + + fp.Path = tryPath } m.Infof("resolved `%s` with source `%s`", fp.Path, sourceFp.Path) @@ -168,7 +179,9 @@ func (m *FileHandleResolutionSystem) loadFileWithSource(fileID, sourceID akara.E component := m.fileHandles.AddFileHandle(fileID) component.Data = data - m.cache.Insert(cacheKey, data, assetCacheEntryWeight) + if err := m.cache.Insert(cacheKey, data, fileHandleCacheEntryWeight); err != nil { + m.Error(err.Error()) + } return true } diff --git a/d2core/d2systems/file_handle_resolver_test.go b/d2core/d2systems/file_handle_resolver_test.go index f10942a1..8eea5948 100644 --- a/d2core/d2systems/file_handle_resolver_test.go +++ b/d2core/d2systems/file_handle_resolver_test.go @@ -11,6 +11,8 @@ import ( ) func Test_FileHandleResolver_Process(t *testing.T) { + const testDataPath = "./testdata/" + cfg := akara.NewWorldConfig() fileTypeResolver := NewFileTypeResolver() @@ -32,9 +34,7 @@ func Test_FileHandleResolver_Process(t *testing.T) { sourceEntity := world.NewEntity() sourceFp := filePaths.AddFilePath(sourceEntity) - sourceFp.Path = "./testdata/" - - //_ = world.Update(0) + sourceFp.Path = testDataPath fileEntity := world.NewEntity() fileFp := filePaths.AddFilePath(fileEntity) diff --git a/d2core/d2systems/file_source_resolver.go b/d2core/d2systems/file_source_resolver.go index f11be701..ed544b3d 100644 --- a/d2core/d2systems/file_source_resolver.go +++ b/d2core/d2systems/file_source_resolver.go @@ -1,11 +1,12 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "os" "path/filepath" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -18,6 +19,7 @@ const ( logPrefixFileSourceResolver = "File Source Resolver" ) +// NewFileSourceResolver creates a new file source resolver system func NewFileSourceResolver() *FileSourceResolver { // subscribe to entities with a file type and file path, but no file source type filesToCheck := akara.NewFilter(). @@ -28,7 +30,7 @@ func NewFileSourceResolver() *FileSourceResolver { fsr := &FileSourceResolver{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToCheck), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } fsr.SetPrefix(logPrefixFileSourceResolver) @@ -36,6 +38,9 @@ func NewFileSourceResolver() *FileSourceResolver { return fsr } +// FileSourceResolver is responsible for determining if files can be used as a file source. +// If it can, it sets the file up as a source, and the file handle resolver system can +// then use the source to open files. type FileSourceResolver struct { *akara.BaseSubscriberSystem *d2util.Logger @@ -45,8 +50,8 @@ type FileSourceResolver struct { fileSources *d2components.FileSourceMap } -// Init initializes the system with the given world -func (m *FileSourceResolver) Init(world *akara.World) { +// Init initializes the file source resolver, injecting the necessary components into the world +func (m *FileSourceResolver) Init(_ *akara.World) { m.Info("initializing ...") m.fileSub = m.Subscriptions[0] @@ -58,7 +63,7 @@ func (m *FileSourceResolver) Init(world *akara.World) { m.fileSources = m.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) } -// Process processes all of the Entities +// Update iterates over entities from its subscription, and checks if it can be used as a file source func (m *FileSourceResolver) Update() { for subIdx := range m.Subscriptions { for _, sourceEntityID := range m.Subscriptions[subIdx].GetEntities() { @@ -91,27 +96,22 @@ func (m *FileSourceResolver) processSourceEntity(id akara.EID) { } source := m.fileSources.AddFileSource(id) + source.AbstractSource = instance m.Infof("using MPQ source for `%s`", fp.Path) - source.AbstractSource = instance case d2enum.FileTypeDirectory: - instance, err := m.makeFileSystemSource(fp.Path) - - if err != nil { - ft.Type = d2enum.FileTypeUnknown - break - } + instance := m.makeFileSystemSource(fp.Path) source := m.fileSources.AddFileSource(id) + source.AbstractSource = instance m.Infof("using FILESYSTEM source for `%s`", fp.Path) - source.AbstractSource = instance } } // filesystem source -func (m *FileSourceResolver) makeFileSystemSource(path string) (d2components.AbstractSource, error) { - return &fsSource{rootDir: path}, nil +func (m *FileSourceResolver) makeFileSystemSource(path string) d2components.AbstractSource { + return &fsSource{rootDir: path} } type fsSource struct { diff --git a/d2core/d2systems/file_source_resolver_test.go b/d2core/d2systems/file_source_resolver_test.go index 5767a423..9dfaf68f 100644 --- a/d2core/d2systems/file_source_resolver_test.go +++ b/d2core/d2systems/file_source_resolver_test.go @@ -10,6 +10,8 @@ import ( ) func Test_FileSourceResolution(t *testing.T) { + const testDataPath = "./testdata/" + cfg := akara.NewWorldConfig() srcResolver := NewFileSourceResolver() @@ -29,7 +31,7 @@ func Test_FileSourceResolution(t *testing.T) { sourceEntity := world.NewEntity() sourceFp := filePaths.AddFilePath(sourceEntity) - sourceFp.Path = "./testdata/" + sourceFp.Path = testDataPath _ = world.Update(0) diff --git a/d2core/d2systems/file_type_resolver.go b/d2core/d2systems/file_type_resolver.go index 619ed2ba..11f9af77 100644 --- a/d2core/d2systems/file_type_resolver.go +++ b/d2core/d2systems/file_type_resolver.go @@ -1,11 +1,12 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "os" "path/filepath" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -28,7 +29,7 @@ func NewFileTypeResolver() *FileTypeResolver { ftr := &FileTypeResolver{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToCheck), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } ftr.SetPrefix(logPrefixFileTypeResolver) @@ -53,7 +54,7 @@ type FileTypeResolver struct { } // Init initializes the system with the given world -func (m *FileTypeResolver) Init(world *akara.World) { +func (m *FileTypeResolver) Init(_ *akara.World) { m.Info("initializing ...") m.filesToCheck = m.Subscriptions[0] @@ -64,13 +65,14 @@ func (m *FileTypeResolver) Init(world *akara.World) { m.FileTypeMap = m.InjectMap(d2components.FileType).(*d2components.FileTypeMap) } -// Process processes all of the Entities +// Update processes all of the Entities func (m *FileTypeResolver) Update() { for _, eid := range m.filesToCheck.GetEntities() { m.determineFileType(eid) } } +//nolint:gocyclo // this big switch statement is unfortunate, but necessary func (m *FileTypeResolver) determineFileType(id akara.EID) { fp, found := m.GetFilePath(id) if !found { diff --git a/d2core/d2systems/game_client_bootstrap.go b/d2core/d2systems/game_client_bootstrap.go index 598df8d9..0f3dc364 100644 --- a/d2core/d2systems/game_client_bootstrap.go +++ b/d2core/d2systems/game_client_bootstrap.go @@ -1,8 +1,6 @@ package d2systems import ( - "errors" - "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" @@ -16,13 +14,14 @@ const ( // static check that the game config system implements the system interface var _ akara.System = &GameClientBootstrapSystem{} +// NewGameClientBootstrapSystem makes a new client bootstrap system func NewGameClientBootstrapSystem() *GameClientBootstrapSystem { // we are interested in actual game config instances, too gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build() sys := &GameClientBootstrapSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(gameConfigs), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } sys.SetPrefix(logPrefixGameClientBootstrap) @@ -30,14 +29,16 @@ func NewGameClientBootstrapSystem() *GameClientBootstrapSystem { return sys } -// GameClientBootstrapSystem is responsible for setting up the regular diablo2 game launch +// GameClientBootstrapSystem is responsible for setting up other +// systems that are common to both the game client and the headless game server type GameClientBootstrapSystem struct { *akara.BaseSubscriberSystem *d2util.Logger *RenderSystem } -func (m *GameClientBootstrapSystem) Init(world *akara.World) { +// Init injects the common systems required by both the game client and headless server +func (m *GameClientBootstrapSystem) Init(_ *akara.World) { m.Info("initializing ...") m.injectSystems() @@ -45,31 +46,20 @@ func (m *GameClientBootstrapSystem) Init(world *akara.World) { m.Info("game client bootstrap complete, deactivating") m.SetActive(false) - if err := m.RenderSystem.Loop(); err != nil { - panic(err) + if err := m.World.Update(0); err != nil { + m.Error(err.Error()) } } func (m *GameClientBootstrapSystem) injectSystems() { m.RenderSystem = NewRenderSystem() + m.World.AddSystem(m.RenderSystem) m.World.AddSystem(NewUpdateCounterSystem()) m.World.AddSystem(NewMainMenuScene()) - m.World.AddSystem(m.RenderSystem) - - loadAttempts := 10 - - for loadAttempts > 0 { - m.World.Update(0) - - loadAttempts-- - - if m.RenderSystem.renderer != nil { - return // we've loaded the config, everything is cool - } - } - - panic(errors.New("could not initialize renderer")) } -func (m *GameClientBootstrapSystem) Update() {} +// Update does nothing, but exists to satisfy the `akara.System` interface +func (m *GameClientBootstrapSystem) Update() { + // nothing to do after init ... +} diff --git a/d2core/d2systems/game_client_test.go b/d2core/d2systems/game_client_test.go deleted file mode 100644 index 49dc8d14..00000000 --- a/d2core/d2systems/game_client_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package d2systems - -import ( - "github.com/gravestench/akara" - "testing" -) - -func Test_game_client(t *testing.T) { - cfg := akara.NewWorldConfig() - - cfg. - With(NewAppBootstrapSystem()). - With(NewGameClientBootstrapSystem()) - - akara.NewWorld(cfg) -} diff --git a/d2core/d2systems/game_config.go b/d2core/d2systems/game_config.go index 6ae30b30..38fdd37c 100644 --- a/d2core/d2systems/game_config.go +++ b/d2core/d2systems/game_config.go @@ -2,6 +2,7 @@ package d2systems import ( "encoding/json" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" @@ -17,6 +18,7 @@ const ( loggerPrefixGameConfig = "Game Config" ) +// NewGameConfigSystem creates a new game config system func NewGameConfigSystem() *GameConfigSystem { // we are going to check entities that dont yet have loaded asset types filesToCheck := akara.NewFilter(). @@ -44,7 +46,7 @@ func NewGameConfigSystem() *GameConfigSystem { gcs := &GameConfigSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToCheck, gameConfigs), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } gcs.SetPrefix(loggerPrefixGameConfig) @@ -76,6 +78,7 @@ type GameConfigSystem struct { ActiveConfig *d2components.GameConfigComponent } +// Init the world with the necessary components related to game config files func (m *GameConfigSystem) Init(world *akara.World) { m.Info("initializing ...") @@ -92,6 +95,7 @@ func (m *GameConfigSystem) Init(world *akara.World) { m.DirtyMap = world.InjectMap(d2components.Dirty).(*d2components.DirtyMap) } +// Update checks for new config files func (m *GameConfigSystem) Update() { m.checkForNewConfig(m.filesToCheck.GetEntities()) } diff --git a/d2core/d2systems/game_config_test.go b/d2core/d2systems/game_config_test.go index 677acba7..d4961e97 100644 --- a/d2core/d2systems/game_config_test.go +++ b/d2core/d2systems/game_config_test.go @@ -9,6 +9,8 @@ import ( ) func Test_GameConfigSystem_Bootstrap(t *testing.T) { + const testDataPath = "./testdata/" + cfg := akara.NewWorldConfig() typeSys := NewFileTypeResolver() @@ -34,7 +36,7 @@ func Test_GameConfigSystem_Bootstrap(t *testing.T) { filePaths := filePathsAbstract.(*d2components.FilePathMap) cfgDir := filePaths.AddFilePath(world.NewEntity()) - cfgDir.Path = "./testdata/" + cfgDir.Path = testDataPath cfgFile := filePaths.AddFilePath(world.NewEntity()) cfgFile.Path = "config.json" diff --git a/d2core/d2systems/game_object_factory.go b/d2core/d2systems/game_object_factory.go index 6ec87544..1298a130 100644 --- a/d2core/d2systems/game_object_factory.go +++ b/d2core/d2systems/game_object_factory.go @@ -1,8 +1,9 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) const ( @@ -13,7 +14,7 @@ const ( func NewGameObjectFactory() *GameObjectFactory { m := &GameObjectFactory{ BaseSystem: &akara.BaseSystem{}, - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } m.SetPrefix(logPrefixGameObjectFactory) @@ -24,7 +25,8 @@ func NewGameObjectFactory() *GameObjectFactory { // static check that GameObjectFactory implements the System interface var _ akara.System = &GameObjectFactory{} -// GameObjectFactory +// GameObjectFactory is a wrapper system for subordinate systems that +// do the actual object creation work. type GameObjectFactory struct { *akara.BaseSystem *d2util.Logger @@ -41,8 +43,7 @@ func (t *GameObjectFactory) injectSubSystems() { t.SpriteFactory = NewSpriteFactorySubsystem(t.BaseSystem, t.Logger) } +// Update updates all the sub-systems func (t *GameObjectFactory) Update() { t.SpriteFactory.Update() } - - diff --git a/d2core/d2systems/movement.go b/d2core/d2systems/movement.go index bc854378..c4042b4c 100644 --- a/d2core/d2systems/movement.go +++ b/d2core/d2systems/movement.go @@ -1,9 +1,10 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "time" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" @@ -19,10 +20,14 @@ func NewMovementSystem() *MovementSystem { filter := cfg.Build() - return &MovementSystem{ + sys := &MovementSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filter), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } + + sys.SetPrefix(logPrefixMovementSystem) + + return sys } // static check that MovementSystem implements the System interface @@ -37,7 +42,7 @@ type MovementSystem struct { } // Init initializes the system with the given world -func (m *MovementSystem) Init(world *akara.World) { +func (m *MovementSystem) Init(_ *akara.World) { m.Info("initializing ...") // try to inject the components we require, then cast the returned @@ -46,7 +51,7 @@ func (m *MovementSystem) Init(world *akara.World) { m.VelocityMap = m.InjectMap(d2components.Velocity).(*d2components.VelocityMap) } -// Process processes all of the Entities +// Update positions of all entities with their velocities func (m *MovementSystem) Update() { for subIdx := range m.Subscriptions { entities := m.Subscriptions[subIdx].GetEntities() diff --git a/d2core/d2systems/movement_test.go b/d2core/d2systems/movement_test.go index 52cad55f..5bdad276 100644 --- a/d2core/d2systems/movement_test.go +++ b/d2core/d2systems/movement_test.go @@ -1,7 +1,6 @@ package d2systems import ( - "fmt" "math/rand" "strconv" "testing" @@ -116,7 +115,7 @@ func TestMovementSystem_Update(t *testing.T) { } } -func bench_N_entities(n int, b *testing.B) { +func benchN(n int, b *testing.B) { cfg := akara.NewWorldConfig() movementSystem := NewMovementSystem() @@ -131,7 +130,7 @@ func bench_N_entities(n int, b *testing.B) { v := movementSystem.AddVelocity(e) p.Set(0, 0) - v.Set(rand.Float64(), rand.Float64()) + v.Set(rand.Float64(), rand.Float64()) //nolint:gosec // it's just a test } benchName := strconv.Itoa(n) + "_entity update" @@ -140,15 +139,13 @@ func bench_N_entities(n int, b *testing.B) { _ = world.Update(time.Millisecond) } }) - - fmt.Println("done!") } func BenchmarkMovementSystem_Update(b *testing.B) { - bench_N_entities(1e1, b) - bench_N_entities(1e2, b) - bench_N_entities(1e3, b) - bench_N_entities(1e4, b) - bench_N_entities(1e5, b) - bench_N_entities(1e6, b) + benchN(1e1, b) + benchN(1e2, b) + benchN(1e3, b) + benchN(1e4, b) + benchN(1e5, b) + benchN(1e6, b) } diff --git a/d2core/d2systems/render.go b/d2core/d2systems/render.go index 26017667..c7d12d23 100644 --- a/d2core/d2systems/render.go +++ b/d2core/d2systems/render.go @@ -2,9 +2,10 @@ package d2systems import ( "errors" + "time" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" - "time" "github.com/gravestench/akara" "github.com/hajimehoshi/ebiten/v2" @@ -15,11 +16,11 @@ import ( ) const ( - gameTitle = "Open Diablo 2" + gameTitle = "Open Diablo 2" logPrefixRenderSystem = "Render System" ) -// NewRenderSystem creates a movement system +// NewRenderSystem creates a new render system func NewRenderSystem() *RenderSystem { viewports := akara.NewFilter(). Require(d2components.Viewport). @@ -31,7 +32,7 @@ func NewRenderSystem() *RenderSystem { r := &RenderSystem{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(viewports, gameConfigs), - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } r.SetPrefix(logPrefixRenderSystem) @@ -42,24 +43,24 @@ func NewRenderSystem() *RenderSystem { // static check that RenderSystem implements the System interface var _ akara.System = &RenderSystem{} -// RenderSystem handles entity movement based on velocity and position components +// RenderSystem is responsible for rendering the main viewports of scenes +// to the game screen. type RenderSystem struct { *akara.BaseSubscriberSystem *d2util.Logger - renderer d2interface.Renderer - screenSurface d2interface.Surface - viewports *akara.Subscription - configs *akara.Subscription + renderer d2interface.Renderer + viewports *akara.Subscription + configs *akara.Subscription *d2components.GameConfigMap *d2components.ViewportMap *d2components.MainViewportMap *d2components.SurfaceMap - lastUpdate time.Time + lastUpdate time.Time gameLoopInitDelay time.Duration // there is a race condition, this is a hack } -// Init initializes the system with the given world -func (m *RenderSystem) Init(world *akara.World) { +// Init initializes the system with the given world, injecting the necessary components +func (m *RenderSystem) Init(_ *akara.World) { m.Info("initializing ...") m.gameLoopInitDelay = time.Millisecond @@ -75,15 +76,30 @@ func (m *RenderSystem) Init(world *akara.World) { m.SurfaceMap = m.InjectMap(d2components.Surface).(*d2components.SurfaceMap) } -// Process will create a renderer if it doesnt exist yet, -// and then +// Update will initialize the renderer, start the game loop, and +// disable the system (to prevent it from being called during the game loop). +// +// The reason why this isn't in the init step is because we use other systems +// for loading the config file, and it may take more than one iteration func (m *RenderSystem) Update() { if m.renderer != nil { - return + return // we already created the renderer } m.createRenderer() + + if m.renderer == nil { + return // the renderer has not yet been created! + } + + // if we have created the renderer, we can safely disable + // this system and start the run loop. m.SetActive(false) + + err := m.startGameLoop() + if err != nil { + m.Fatal(err.Error()) + } } func (m *RenderSystem) createRenderer() { @@ -99,10 +115,9 @@ func (m *RenderSystem) createRenderer() { return } - // d2render.CreateRenderer should use a GameConfigComponent instead ... + // we should get rid of d2config.Configuration and use components instead... oldStyleConfig := &d2config.Configuration{ MpqLoadOrder: config.MpqLoadOrder, - Language: config.Language, MpqPath: config.MpqPath, TicksPerSecond: config.TicksPerSecond, FpsCap: config.FpsCap, @@ -117,8 +132,7 @@ func (m *RenderSystem) createRenderer() { renderer, err := d2render.CreateRenderer(oldStyleConfig) if err != nil { - m.Error(err.Error()) - panic(err) + m.Fatal(err.Error()) } // HACK: hardcoded with ebiten for now @@ -147,6 +161,10 @@ func (m *RenderSystem) render(screen d2interface.Surface) error { return errors.New("main viewport doesn't have a surface") } + if sfc.Surface == nil { + sfc.Surface = m.renderer.NewSurface(vp.Width, vp.Height) + } + screen.PushTranslation(vp.Left, vp.Top) screen.Render(sfc.Surface) screen.Pop() @@ -168,8 +186,8 @@ func (m *RenderSystem) updateWorld() error { return m.World.Update(elapsed) } -func (m *RenderSystem) Loop() error { - m.Infof("entering game run loop ...") +func (m *RenderSystem) startGameLoop() error { + m.Infof("starting game loop ...") return m.renderer.Run(m.render, m.updateWorld, 800, 600, gameTitle) } diff --git a/d2core/d2systems/render_test.go b/d2core/d2systems/render_test.go deleted file mode 100644 index 3ea0773e..00000000 --- a/d2core/d2systems/render_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package d2systems - -import ( - "github.com/gravestench/akara" - "image/color" - "math" - "testing" - "time" -) - -func Test_RenderSystem(t *testing.T) { - cfg := akara.NewWorldConfig() - - renderSys := NewRenderSystem() - - cfg.With(renderSys) - - world := akara.NewWorld(cfg) - - gameConfigEntity := world.NewEntity() - - renderSys.AddGameConfig(gameConfigEntity) - - vpEntity := world.NewEntity() - vp := renderSys.AddViewport(vpEntity) - - vp.Width = 400 - vp.Height = 300 - - renderSys.AddMainViewport(vpEntity) - - sfc := renderSys.AddSurface(vpEntity) - - loadAttempts := 10 - for loadAttempts > 0 { - if renderSys.renderer != nil { - break - } - - _ = world.Update(0) - - loadAttempts-- - } - - if loadAttempts < 0 { - t.Fatal("could not create renderer") - } - - sfc.Surface = renderSys.renderer.NewSurface(400, 300) - sfc.Surface.DrawRect(20, 20, color.RGBA{100, 100, 255, 255}) - - go func(){ - x, y := 0.0, 0.0 - - for { - ms := float64(world.TimeDelta.Milliseconds()) - x, y = x+ms/1000, y+ms/3000 - vp.Top = int(math.Abs(math.Sin(y) * 300)) - vp.Left = int(math.Abs(math.Cos(x) * 400)) - time.Sleep(time.Second/60) - } - }() - - err := renderSys.Loop() - if err != nil { - t.Fatal(err) - } -} diff --git a/d2core/d2systems/scene_base.go b/d2core/d2systems/scene_base.go index bb5054b1..8fc2043b 100644 --- a/d2core/d2systems/scene_base.go +++ b/d2core/d2systems/scene_base.go @@ -1,10 +1,11 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/gravestench/akara" "path/filepath" + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) @@ -13,14 +14,15 @@ const ( mainViewport int = 0 ) +// NewBaseScene creates a new base scene instance func NewBaseScene(key string) *BaseScene { base := &BaseScene{ - BaseSystem: &akara.BaseSystem{}, - Logger: d2util.NewLogger(), - key: key, - Viewports: make([]akara.EID, 0), + BaseSystem: &akara.BaseSystem{}, + Logger: d2util.NewLogger(), + key: key, + Viewports: make([]akara.EID, 0), GameObjects: make([]akara.EID, 0), - systems: &baseSystems{}, + systems: &baseSystems{}, } base.SetPrefix(key) @@ -35,15 +37,20 @@ type baseSystems struct { *GameObjectFactory } +// BaseScene encapsulates common behaviors for systems that are considered "scenes", +// such as the main menu, the in-game map, the console, etc. +// +// The base scene is responsible for generic behaviors common to all scenes, +// like initializing the default viewport, or rendering game objects to the viewports. type BaseScene struct { *akara.BaseSystem *d2util.Logger - key string - booted bool - paused bool - systems *baseSystems - Add *sceneObjectAssigner - Viewports []akara.EID + key string + booted bool + paused bool + systems *baseSystems + Add *sceneObjectAssigner + Viewports []akara.EID GameObjects []akara.EID *d2components.MainViewportMap *d2components.ViewportMap @@ -55,14 +62,17 @@ type BaseScene struct { *d2components.AnimationMap } +// Booted returns whether or not the scene has booted func (s *BaseScene) Booted() bool { return s.booted } +// Paused returns whether or not the scene is paused func (s *BaseScene) Paused() bool { return s.paused } +// Init the base scene func (s *BaseScene) Init(world *akara.World) { s.World = world @@ -125,6 +135,7 @@ func (s *BaseScene) injectComponentMaps() { } func (s *BaseScene) createDefaultViewport() { + s.Info("creating default viewport") viewportID := s.NewEntity() s.AddViewport(viewportID) @@ -133,16 +144,18 @@ func (s *BaseScene) createDefaultViewport() { camera.Height = 600 camera.Zoom = 1 - s.AddSurface(viewportID) + s.AddSurface(viewportID).Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height) s.AddMainViewport(viewportID) s.Viewports = append(s.Viewports, viewportID) } +// Key returns the scene's key func (s *BaseScene) Key() string { return s.key } +// Update performs scene boot and renders the scene viewports func (s *BaseScene) Update() { if !s.booted { s.boot() @@ -152,27 +165,29 @@ func (s *BaseScene) Update() { return } - s.RenderViewports() + s.renderViewports() } -func (s *BaseScene) RenderViewports() { +func (s *BaseScene) renderViewports() { if s.systems.RenderSystem == nil { + s.Warning("render system not present") return } if s.systems.RenderSystem.renderer == nil { + s.Warning("render system doesn't have a renderer instance") return } numViewports := len(s.Viewports) if numViewports < 1 { - return + s.createDefaultViewport() } viewportObjects := s.binGameObjectsByViewport() - for idx := numViewports-1; idx >= 0; idx-- { + for idx := numViewports - 1; idx >= 0; idx-- { s.renderViewport(idx, viewportObjects[idx]) } } @@ -225,10 +240,10 @@ func (s *BaseScene) renderViewport(idx int, objects []akara.EID) { sfc.Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height) } - cx, cy := int(camera.X()) + camera.Width>>1, int(camera.Y()) + camera.Height>>1 + cx, cy := int(camera.X())+camera.Width>>1, int(camera.Y())+camera.Height>>1 sfc.Surface.PushTranslation(-cx, -cy) // negative because we're offsetting everything that gets rendered - sfc.Surface.PushScale(-camera.Zoom, -camera.Zoom) + sfc.Surface.PushScale(camera.Zoom, camera.Zoom) for _, object := range objects { s.renderObject(sfc.Surface, object) diff --git a/d2core/d2systems/scene_main_menu.go b/d2core/d2systems/scene_main_menu.go index 21e9de22..e70d520d 100644 --- a/d2core/d2systems/scene_main_menu.go +++ b/d2core/d2systems/scene_main_menu.go @@ -1,70 +1,69 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - //"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" ) const ( - SceneKeyMainMenu = "Main Menu" + sceneKeyMainMenu = "Main Menu" ) -func NewMainMenuScene() *Scene { - scene := &Scene{ - BaseScene: NewBaseScene(SceneKeyMainMenu), +// NewMainMenuScene creates a new main menu scene. This is the first screen that the user +// will see when launching the game. +func NewMainMenuScene() *MainMenuScene { + scene := &MainMenuScene{ + BaseScene: NewBaseScene(sceneKeyMainMenu), } return scene } // static check that MainMenuScene implements the scene interface -var _ d2interface.Scene = &Scene{} +var _ d2interface.Scene = &MainMenuScene{} -type Scene struct { +// MainMenuScene represents the game's main menu, where users can select single or multi player, +// or start the map engine test. +type MainMenuScene struct { *BaseScene booted bool } -func (s *Scene) Init(world *akara.World) { +// Init the main menu scene +func (s *MainMenuScene) Init(_ *akara.World) { s.Info("initializing ...") } -func (s *Scene) boot() { +func (s *MainMenuScene) boot() { if !s.BaseScene.booted { return } s.createBackground() - //s.createButtons() - //s.createTrademarkScreen() // done last so that it's on top of everything + s.createButtons() + s.createTrademarkScreen() s.booted = true } -func (s *Scene) createBackground() { +func (s *MainMenuScene) createBackground() { s.Info("creating background") s.Add.Sprite(0, 0, d2resource.GameSelectScreen, d2resource.PaletteSky) } -func (s *Scene) createButtons() { +func (s *MainMenuScene) createButtons() { s.Info("creating buttons") - } -func (s *Scene) createTrademarkScreen() { +func (s *MainMenuScene) createTrademarkScreen() { s.Info("creating trademark screen") - //image := s.Add.Sprite(0, 0, d2resource.TrademarkScreen, d2resource.PaletteSky). - // SetOrigin(0, 0). - // SetDisplaySize(s.Camera.Width, s.Camera.Height). - // SetInteractive(true, nil) - // - //s.trademark = image + s.Add.Sprite(0, 0, d2resource.TrademarkScreen, d2resource.PaletteSky) } -func (s *Scene) Update() { +// Update the main menu scene +func (s *MainMenuScene) Update() { if s.Paused() { return } @@ -75,552 +74,3 @@ func (s *Scene) Update() { s.BaseScene.Update() } - -type mainMenuScreenMode int - -//// mainMenuScreenMode types -//const ( -// ScreenModeUnknown mainMenuScreenMode = iota -// ScreenModeTrademark -// ScreenModeMainMenu -// ScreenModeMultiplayer -// ScreenModeTCPIP -// ScreenModeServerIP -//) -// -//const ( -// joinGameDialogX, joinGameDialogY = 318, 245 -// serverIPbackgroundX, serverIPbackgroundY = 270, 175 -// backgroundX, backgroundY = 0, 0 -// versionLabelX, versionLabelY = 795, -10 -// commitLabelX, commitLabelY = 2, 2 -// copyrightX, copyrightY = 400, 500 -// copyright2X, copyright2Y = 400, 525 -// od2LabelX, od2LabelY = 400, 580 -// tcpOptionsX, tcpOptionsY = 400, 23 -// joinGameX, joinGameY = 400, 190 -// diabloLogoX, diabloLogoY = 400, 120 -// exitDiabloBtnX, exitDiabloBtnY = 264, 535 -// creditBtnX, creditBtnY = 264, 505 -// cineBtnX, cineBtnY = 401, 505 -// singlePlayerBtnX, singlePlayerBtnY = 264, 290 -// githubBtnX, githubBtnY = 264, 400 -// mapTestBtnX, mapTestBtnY = 264, 440 -// tcpBtnX, tcpBtnY = 33, 543 -// srvCancelBtnX, srvCancelBtnY = 285, 305 -// srvOkBtnX, srvOkBtnY = 420, 305 -// multiplayerBtnX, multiplayerBtnY = 264, 330 -// tcpNetBtnX, tcpNetBtnY = 264, 280 -// networkCancelBtnX, networkCancelBtnY = 264, 540 -// tcpHostBtnX, tcpHostBtnY = 264, 280 -// tcpJoinBtnX, tcpJoinBtnY = 264, 320 -//) -// -//const ( -// white = 0xffffffff -// lightYellow = 0xffff8cff -// gold = 0xd8c480ff -//) -// -//const ( -// joinGameCharacterFilter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._:" -//) - -//// BuildInfo contains information about the current build -//type BuildInfo struct { -// Branch, Commit string -//} -// -//// MainMenu represents the main menu -//type MainMenu struct { -// tcpIPBackground *d2ui.Sprite -// trademarkBackground *d2ui.Sprite -// background *d2ui.Sprite -// diabloLogoLeft *d2ui.Sprite -// diabloLogoRight *d2ui.Sprite -// diabloLogoLeftBack *d2ui.Sprite -// diabloLogoRightBack *d2ui.Sprite -// serverIPBackground *d2ui.Sprite -// singlePlayerButton *d2ui.Button -// multiplayerButton *d2ui.Button -// githubButton *d2ui.Button -// exitDiabloButton *d2ui.Button -// creditsButton *d2ui.Button -// cinematicsButton *d2ui.Button -// mapTestButton *d2ui.Button -// networkTCPIPButton *d2ui.Button -// networkCancelButton *d2ui.Button -// btnTCPIPCancel *d2ui.Button -// btnTCPIPHostGame *d2ui.Button -// btnTCPIPJoinGame *d2ui.Button -// btnServerIPCancel *d2ui.Button -// btnServerIPOk *d2ui.Button -// copyrightLabel *d2ui.Label -// copyrightLabel2 *d2ui.Label -// openDiabloLabel *d2ui.Label -// versionLabel *d2ui.Label -// commitLabel *d2ui.Label -// tcpIPOptionsLabel *d2ui.Label -// tcpJoinGameLabel *d2ui.Label -// tcpJoinGameEntry *d2ui.TextBox -// screenMode mainMenuScreenMode -// leftButtonHeld bool -// -// asset *d2asset.AssetManager -// inputManager d2interface.InputManager -// renderer d2interface.Renderer -// audioProvider d2interface.AudioProvider -// scriptEngine *d2script.ScriptEngine -// navigator d2interface.Navigator -// uiManager *d2ui.UIManager -// heroState *d2hero.HeroStateFactory -// -// buildInfo BuildInfo -//} -// -//// CreateMainMenu creates an instance of MainMenu -//func CreateMainMenu( -// navigator d2interface.Navigator, -// asset *d2asset.AssetManager, -// renderer d2interface.Renderer, -// inputManager d2interface.InputManager, -// audioProvider d2interface.AudioProvider, -// ui *d2ui.UIManager, -// buildInfo BuildInfo, -//) (*MainMenu, error) { -// heroStateFactory, err := d2hero.NewHeroStateFactory(asset) -// if err != nil { -// return nil, err -// } -// -// mainMenu := &MainMenu{ -// asset: asset, -// screenMode: ScreenModeUnknown, -// leftButtonHeld: true, -// renderer: renderer, -// inputManager: inputManager, -// audioProvider: audioProvider, -// navigator: navigator, -// buildInfo: buildInfo, -// uiManager: ui, -// heroState: heroStateFactory, -// } -// -// return mainMenu, nil -//} -// -//// OnLoad is called to load the resources for the main menu -//func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { -// v.audioProvider.PlayBGM(d2resource.BGMTitle) -// loading.Progress(twentyPercent) -// -// v.createLabels(loading) -// v.loadBackgroundSprites() -// v.createLogos(loading) -// v.createButtons(loading) -// -// v.tcpJoinGameEntry = v.uiManager.NewTextbox() -// v.tcpJoinGameEntry.SetPosition(joinGameDialogX, joinGameDialogY) -// v.tcpJoinGameEntry.SetFilter(joinGameCharacterFilter) -// loading.Progress(ninetyPercent) -// -// if v.screenMode == ScreenModeUnknown { -// v.SetScreenMode(ScreenModeTrademark) -// } else { -// v.SetScreenMode(ScreenModeMainMenu) -// } -// -// if err := v.inputManager.BindHandler(v); err != nil { -// fmt.Println("failed to add main menu as event handler") -// } -//} -// -//func (v *MainMenu) loadBackgroundSprites() { -// var err error -// v.background, err = v.uiManager.NewSprite(d2resource.GameSelectScreen, d2resource.PaletteSky) -// if err != nil { -// log.Print(err) -// } -// v.background.SetPosition(backgroundX, backgroundY) -// -// v.trademarkBackground, err = v.uiManager.NewSprite(d2resource.TrademarkScreen, d2resource.PaletteSky) -// if err != nil { -// log.Print(err) -// } -// v.trademarkBackground.SetPosition(backgroundX, backgroundY) -// -// v.tcpIPBackground, err = v.uiManager.NewSprite(d2resource.TCPIPBackground, d2resource.PaletteSky) -// if err != nil { -// log.Print(err) -// } -// v.tcpIPBackground.SetPosition(backgroundX, backgroundY) -// -// v.serverIPBackground, err = v.uiManager.NewSprite(d2resource.PopUpOkCancel, d2resource.PaletteFechar) -// if err != nil { -// log.Print(err) -// } -// v.serverIPBackground.SetPosition(serverIPbackgroundX, serverIPbackgroundY) -//} -// -//func (v *MainMenu) createLabels(loading d2screen.LoadingState) { -// v.versionLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) -// v.versionLabel.Alignment = d2gui.HorizontalAlignRight -// v.versionLabel.SetText("OpenDiablo2 - " + v.buildInfo.Branch) -// v.versionLabel.Color[0] = d2util.Color(white) -// v.versionLabel.SetPosition(versionLabelX, versionLabelY) -// -// v.commitLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic) -// v.commitLabel.Alignment = d2gui.HorizontalAlignLeft -// v.commitLabel.SetText(v.buildInfo.Commit) -// v.commitLabel.Color[0] = d2util.Color(white) -// v.commitLabel.SetPosition(commitLabelX, commitLabelY) -// -// v.copyrightLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) -// v.copyrightLabel.Alignment = d2gui.HorizontalAlignCenter -// v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") -// v.copyrightLabel.Color[0] = d2util.Color(lightBrown) -// v.copyrightLabel.SetPosition(copyrightX, copyrightY) -// loading.Progress(thirtyPercent) -// -// v.copyrightLabel2 = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic) -// v.copyrightLabel2.Alignment = d2gui.HorizontalAlignCenter -// v.copyrightLabel2.SetText("All Rights Reserved.") -// v.copyrightLabel2.Color[0] = d2util.Color(lightBrown) -// v.copyrightLabel2.SetPosition(copyright2X, copyright2Y) -// -// v.openDiabloLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic) -// v.openDiabloLabel.Alignment = d2gui.HorizontalAlignCenter -// v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision") -// v.openDiabloLabel.Color[0] = d2util.Color(lightYellow) -// v.openDiabloLabel.SetPosition(od2LabelX, od2LabelY) -// loading.Progress(fiftyPercent) -// -// v.tcpIPOptionsLabel = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits) -// v.tcpIPOptionsLabel.SetPosition(tcpOptionsX, tcpOptionsY) -// v.tcpIPOptionsLabel.Alignment = d2gui.HorizontalAlignCenter -// v.tcpIPOptionsLabel.SetText("TCP/IP Options") -// -// v.tcpJoinGameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) -// v.tcpJoinGameLabel.Alignment = d2gui.HorizontalAlignCenter -// v.tcpJoinGameLabel.SetText("Enter Host IP Address\nto Join Game") -// -// v.tcpJoinGameLabel.Color[0] = d2util.Color(gold) -// v.tcpJoinGameLabel.SetPosition(joinGameX, joinGameY) -//} -// -//func (v *MainMenu) createLogos(loading d2screen.LoadingState) { -// var err error -// v.diabloLogoLeft, err = v.uiManager.NewSprite(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits) -// if err != nil { -// log.Print(err) -// } -// v.diabloLogoLeft.SetEffect(d2enum.DrawEffectModulate) -// v.diabloLogoLeft.PlayForward() -// v.diabloLogoLeft.SetPosition(diabloLogoX, diabloLogoY) -// loading.Progress(sixtyPercent) -// -// v.diabloLogoRight, err = v.uiManager.NewSprite(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits) -// if err != nil { -// log.Print(err) -// } -// v.diabloLogoRight.SetEffect(d2enum.DrawEffectModulate) -// v.diabloLogoRight.PlayForward() -// v.diabloLogoRight.SetPosition(diabloLogoX, diabloLogoY) -// -// v.diabloLogoLeftBack, err = v.uiManager.NewSprite(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits) -// if err != nil { -// log.Print(err) -// } -// v.diabloLogoLeftBack.SetPosition(diabloLogoX, diabloLogoY) -// -// v.diabloLogoRightBack, err = v.uiManager.NewSprite(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits) -// if err != nil { -// log.Print(err) -// } -// v.diabloLogoRightBack.SetPosition(diabloLogoX, diabloLogoY) -//} -// -//func (v *MainMenu) createButtons(loading d2screen.LoadingState) { -// v.exitDiabloButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "EXIT DIABLO II") -// v.exitDiabloButton.SetPosition(exitDiabloBtnX, exitDiabloBtnY) -// v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() }) -// -// v.creditsButton = v.uiManager.NewButton(d2ui.ButtonTypeShort, "CREDITS") -// v.creditsButton.SetPosition(creditBtnX, creditBtnY) -// v.creditsButton.OnActivated(func() { v.onCreditsButtonClicked() }) -// -// v.cinematicsButton = v.uiManager.NewButton(d2ui.ButtonTypeShort, "CINEMATICS") -// v.cinematicsButton.SetPosition(cineBtnX, cineBtnY) -// loading.Progress(seventyPercent) -// -// v.singlePlayerButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "SINGLE PLAYER") -// v.singlePlayerButton.SetPosition(singlePlayerBtnX, singlePlayerBtnY) -// v.singlePlayerButton.OnActivated(func() { v.onSinglePlayerClicked() }) -// -// v.githubButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "PROJECT WEBSITE") -// v.githubButton.SetPosition(githubBtnX, githubBtnY) -// v.githubButton.OnActivated(func() { v.onGithubButtonClicked() }) -// -// v.mapTestButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "MAP ENGINE TEST") -// v.mapTestButton.SetPosition(mapTestBtnX, mapTestBtnY) -// v.mapTestButton.OnActivated(func() { v.onMapTestClicked() }) -// -// v.btnTCPIPCancel = v.uiManager.NewButton(d2ui.ButtonTypeMedium, -// d2tbl.TranslateString("cancel")) -// v.btnTCPIPCancel.SetPosition(tcpBtnX, tcpBtnY) -// v.btnTCPIPCancel.OnActivated(func() { v.onTCPIPCancelClicked() }) -// -// v.btnServerIPCancel = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "CANCEL") -// v.btnServerIPCancel.SetPosition(srvCancelBtnX, srvCancelBtnY) -// v.btnServerIPCancel.OnActivated(func() { v.onBtnTCPIPCancelClicked() }) -// -// v.btnServerIPOk = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "OK") -// v.btnServerIPOk.SetPosition(srvOkBtnX, srvOkBtnY) -// v.btnServerIPOk.OnActivated(func() { v.onBtnTCPIPOkClicked() }) -// -// v.createMultiplayerMenuButtons() -// loading.Progress(eightyPercent) -//} -// -//func (v *MainMenu) createMultiplayerMenuButtons() { -// v.multiplayerButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "MULTIPLAYER") -// v.multiplayerButton.SetPosition(multiplayerBtnX, multiplayerBtnY) -// v.multiplayerButton.OnActivated(func() { v.onMultiplayerClicked() }) -// -// v.networkTCPIPButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "TCP/IP GAME") -// v.networkTCPIPButton.SetPosition(tcpNetBtnX, tcpNetBtnY) -// v.networkTCPIPButton.OnActivated(func() { v.onNetworkTCPIPClicked() }) -// -// v.networkCancelButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, -// d2tbl.TranslateString("cancel")) -// v.networkCancelButton.SetPosition(networkCancelBtnX, networkCancelBtnY) -// v.networkCancelButton.OnActivated(func() { v.onNetworkCancelClicked() }) -// -// v.btnTCPIPHostGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, "HOST GAME") -// v.btnTCPIPHostGame.SetPosition(tcpHostBtnX, tcpHostBtnY) -// v.btnTCPIPHostGame.OnActivated(func() { v.onTCPIPHostGameClicked() }) -// -// v.btnTCPIPJoinGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, "JOIN GAME") -// v.btnTCPIPJoinGame.SetPosition(tcpJoinBtnX, tcpJoinBtnY) -// v.btnTCPIPJoinGame.OnActivated(func() { v.onTCPIPJoinGameClicked() }) -//} -// -//func (v *MainMenu) onMapTestClicked() { -// v.navigator.ToMapEngineTest(0, 1) -//} -// -//func (v *MainMenu) onSinglePlayerClicked() { -// if v.heroState.HasGameStates() { -// // Go here only if existing characters are available to select -// v.navigator.ToCharacterSelect(d2clientconnectiontype.Local, v.tcpJoinGameEntry.GetText()) -// } else { -// v.navigator.ToSelectHero(d2clientconnectiontype.Local, v.tcpJoinGameEntry.GetText()) -// } -//} -// -//func (v *MainMenu) onGithubButtonClicked() { -// url := "https://www.github.com/OpenDiablo2/OpenDiablo2" -// -// var err error -// -// switch runtime.GOOS { -// case "linux": -// err = exec.Command("xdg-open", url).Start() -// case "windows": -// err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() -// case "darwin": -// err = exec.Command("open", url).Start() -// default: -// err = fmt.Errorf("unsupported platform") -// } -// -// if err != nil { -// log.Fatal(err) -// } -//} -// -//func (v *MainMenu) onExitButtonClicked() { -// os.Exit(0) -//} -// -//func (v *MainMenu) onCreditsButtonClicked() { -// v.navigator.ToCredits() -//} -// -//// Render renders the main menu -//func (v *MainMenu) Render(screen d2interface.Surface) error { -// if err := v.renderBackgrounds(screen); err != nil { -// return err -// } -// -// if err := v.renderLogos(screen); err != nil { -// return err -// } -// -// if err := v.renderLabels(screen); err != nil { -// return err -// } -// -// return nil -//} -// -//func (v *MainMenu) renderBackgrounds(screen d2interface.Surface) error { -// switch v.screenMode { -// case ScreenModeTrademark: -// if err := v.trademarkBackground.RenderSegmented(screen, 4, 3, 0); err != nil { -// return err -// } -// case ScreenModeServerIP: -// if err := v.serverIPBackground.RenderSegmented(screen, 2, 1, 0); err != nil { -// return err -// } -// case ScreenModeTCPIP: -// if err := v.tcpIPBackground.RenderSegmented(screen, 4, 3, 0); err != nil { -// return err -// } -// default: -// if err := v.background.RenderSegmented(screen, 4, 3, 0); err != nil { -// return err -// } -// } -// -// return nil -//} -// -//func (v *MainMenu) renderLogos(screen d2interface.Surface) error { -// switch v.screenMode { -// case ScreenModeTrademark, ScreenModeMainMenu, ScreenModeMultiplayer: -// if err := v.diabloLogoLeftBack.Render(screen); err != nil { -// return err -// } -// -// if err := v.diabloLogoRightBack.Render(screen); err != nil { -// return err -// } -// -// if err := v.diabloLogoLeft.Render(screen); err != nil { -// return err -// } -// -// if err := v.diabloLogoRight.Render(screen); err != nil { -// return err -// } -// } -// -// return nil -//} -// -//func (v *MainMenu) renderLabels(screen d2interface.Surface) error { -// switch v.screenMode { -// case ScreenModeServerIP: -// v.tcpIPOptionsLabel.Render(screen) -// v.tcpJoinGameLabel.Render(screen) -// case ScreenModeTCPIP: -// v.tcpIPOptionsLabel.Render(screen) -// case ScreenModeTrademark: -// v.copyrightLabel.Render(screen) -// v.copyrightLabel2.Render(screen) -// case ScreenModeMainMenu: -// v.openDiabloLabel.Render(screen) -// v.versionLabel.Render(screen) -// v.commitLabel.Render(screen) -// } -// -// return nil -//} -// -//// Advance runs the update logic on the main menu -//func (v *MainMenu) Advance(tickTime float64) error { -// switch v.screenMode { -// case ScreenModeMainMenu, ScreenModeTrademark, ScreenModeMultiplayer: -// if err := v.diabloLogoLeftBack.Advance(tickTime); err != nil { -// return err -// } -// -// if err := v.diabloLogoRightBack.Advance(tickTime); err != nil { -// return err -// } -// -// if err := v.diabloLogoLeft.Advance(tickTime); err != nil { -// return err -// } -// -// if err := v.diabloLogoRight.Advance(tickTime); err != nil { -// return err -// } -// } -// -// return nil -//} -// -//// OnMouseButtonDown is called when a mouse button is clicked -//func (v *MainMenu) OnMouseButtonDown(event d2interface.MouseEvent) bool { -// if v.screenMode == ScreenModeTrademark && event.Button() == d2enum.MouseButtonLeft { -// v.SetScreenMode(ScreenModeMainMenu) -// return true -// } -// -// return false -//} -// -//// SetScreenMode sets the screen mode (which sub-menu the screen is on) -//func (v *MainMenu) SetScreenMode(screenMode mainMenuScreenMode) { -// v.screenMode = screenMode -// isMainMenu := screenMode == ScreenModeMainMenu -// isMultiplayer := screenMode == ScreenModeMultiplayer -// isTCPIP := screenMode == ScreenModeTCPIP -// isServerIP := screenMode == ScreenModeServerIP -// -// v.exitDiabloButton.SetVisible(isMainMenu) -// v.creditsButton.SetVisible(isMainMenu) -// v.cinematicsButton.SetVisible(isMainMenu) -// v.singlePlayerButton.SetVisible(isMainMenu) -// v.githubButton.SetVisible(isMainMenu) -// v.mapTestButton.SetVisible(isMainMenu) -// v.multiplayerButton.SetVisible(isMainMenu) -// v.networkTCPIPButton.SetVisible(isMultiplayer) -// v.networkCancelButton.SetVisible(isMultiplayer) -// v.btnTCPIPCancel.SetVisible(isTCPIP) -// v.btnTCPIPHostGame.SetVisible(isTCPIP) -// v.btnTCPIPJoinGame.SetVisible(isTCPIP) -// v.tcpJoinGameEntry.SetVisible(isServerIP) -// -// if isServerIP { -// v.tcpJoinGameEntry.Activate() -// } -// -// v.btnServerIPOk.SetVisible(isServerIP) -// v.btnServerIPCancel.SetVisible(isServerIP) -//} -// -//func (v *MainMenu) onNetworkCancelClicked() { -// v.SetScreenMode(ScreenModeMainMenu) -//} -// -//func (v *MainMenu) onMultiplayerClicked() { -// v.SetScreenMode(ScreenModeMultiplayer) -//} -// -//func (v *MainMenu) onNetworkTCPIPClicked() { -// v.SetScreenMode(ScreenModeTCPIP) -//} -// -//func (v *MainMenu) onTCPIPCancelClicked() { -// v.SetScreenMode(ScreenModeMultiplayer) -//} -// -//func (v *MainMenu) onTCPIPHostGameClicked() { -// v.navigator.ToCharacterSelect(d2clientconnectiontype.LANServer, "") -//} -// -//func (v *MainMenu) onTCPIPJoinGameClicked() { -// v.SetScreenMode(ScreenModeServerIP) -//} -// -//func (v *MainMenu) onBtnTCPIPCancelClicked() { -// v.SetScreenMode(ScreenModeTCPIP) -//} -// -//func (v *MainMenu) onBtnTCPIPOkClicked() { -// v.navigator.ToCharacterSelect(d2clientconnectiontype.LANClient, v.tcpJoinGameEntry.GetText()) -//} diff --git a/d2core/d2systems/sprite_factory.go b/d2core/d2systems/sprite_factory.go index 3c120bfc..b17231f9 100644 --- a/d2core/d2systems/sprite_factory.go +++ b/d2core/d2systems/sprite_factory.go @@ -13,15 +13,17 @@ const ( fmtCreateSpriteErr = "could not create sprite from image `%s` and palette `%s`" ) +// NewSpriteFactorySubsystem creates a new sprite factory which is intended +// to be embedded in the game object factory system. func NewSpriteFactorySubsystem(b *akara.BaseSystem, l *d2util.Logger) *SpriteFactory { spritesToRender := akara.NewFilter(). Require(d2components.Animation). // we want to process entities that have an animation ... - Forbid(d2components.Surface). // ... but are missing a surface + Forbid(d2components.Surface). // ... but are missing a surface Build() sys := &SpriteFactory{ BaseSubscriberSystem: akara.NewBaseSubscriberSystem(spritesToRender), - Logger: l, + Logger: l, } sys.BaseSystem = b @@ -37,6 +39,8 @@ type spriteLoadQueueEntry struct { type spriteLoadQueue = map[akara.EID]spriteLoadQueueEntry +// SpriteFactory is responsible for queueing sprites to be loaded (as animations), +// as well as binding the animation to a renderer if one is present (which generates the sprite surfaces). type SpriteFactory struct { *akara.BaseSubscriberSystem *d2util.Logger @@ -52,10 +56,11 @@ type SpriteFactory struct { spritesToRender *akara.Subscription } +// Init the sprite factory, injecting the necessary components func (t *SpriteFactory) Init(world *akara.World) { t.Info("initializing sprite factory ...") - t.loadQueue = make(spriteLoadQueue, 0) + t.loadQueue = make(spriteLoadQueue) t.spritesToRender = t.Subscriptions[0] @@ -68,6 +73,8 @@ func (t *SpriteFactory) Init(world *akara.World) { t.SurfaceMap = t.InjectMap(d2components.Surface).(*d2components.SurfaceMap) } +// Update processes the load queue which attempting to create animations, as well as +// binding existing animations to a renderer if one is present. func (t *SpriteFactory) Update() { for _, eid := range t.spritesToRender.GetEntities() { t.tryRenderingSprite(eid) @@ -78,6 +85,7 @@ func (t *SpriteFactory) Update() { } } +// Sprite queues a sprite animation to be loaded func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID { spriteID := t.NewEntity() @@ -153,6 +161,10 @@ func (t *SpriteFactory) tryRenderingSprite(eid akara.EID) { return } + if anim.Animation == nil { + return + } + anim.BindRenderer(t.renderer) sfc := anim.GetCurrentFrameSurface() diff --git a/d2core/d2systems/timescale.go b/d2core/d2systems/timescale.go index c5d7d39b..a354802a 100644 --- a/d2core/d2systems/timescale.go +++ b/d2core/d2systems/timescale.go @@ -1,9 +1,10 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "time" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/gravestench/akara" ) @@ -19,7 +20,7 @@ const ( func NewTimeScaleSystem() *TimeScaleSystem { m := &TimeScaleSystem{ BaseSystem: &akara.BaseSystem{}, - Logger: d2util.NewLogger(), + Logger: d2util.NewLogger(), } m.SetPrefix(logPrefixTimeScaleSystem) @@ -36,7 +37,7 @@ var _ akara.System = &TimeScaleSystem{} type TimeScaleSystem struct { *akara.BaseSystem *d2util.Logger - scale float64 + scale float64 lastScale float64 } @@ -49,9 +50,9 @@ func (t *TimeScaleSystem) Init(world *akara.World) { t.scale = defaultScale } -// Process scales the worlds time delta for this frame +// Update scales the worlds time delta for this frame func (t *TimeScaleSystem) Update() { - if !t.Active() || t.scale == t.lastScale{ + if !t.Active() || t.scale == t.lastScale { return } diff --git a/d2core/d2systems/timescale_test.go b/d2core/d2systems/timescale_test.go index 91075144..2691de8f 100644 --- a/d2core/d2systems/timescale_test.go +++ b/d2core/d2systems/timescale_test.go @@ -33,7 +33,9 @@ func TestTimeScaleSystem_Process(t *testing.T) { actual := time.Second expected := time.Duration(timescaleSystem.scale) * actual - world.Update(actual) + if err := world.Update(actual); err != nil { + timescaleSystem.Error(err.Error()) + } if world.TimeDelta != expected { t.Error("world time delta not scaled") diff --git a/d2core/d2systems/update_counter.go b/d2core/d2systems/update_counter.go index c8104898..212c1da9 100644 --- a/d2core/d2systems/update_counter.go +++ b/d2core/d2systems/update_counter.go @@ -1,14 +1,16 @@ package d2systems import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) const ( logPrefixUpdateCounter = "Update Counter" ) +// NewUpdateCounterSystem creates a new update counter system func NewUpdateCounterSystem() *UpdateCounter { uc := &UpdateCounter{ BaseSystem: &akara.BaseSystem{}, @@ -22,6 +24,7 @@ func NewUpdateCounterSystem() *UpdateCounter { var _ akara.System = &UpdateCounter{} +// UpdateCounter is a utility system that logs the number of updates per second type UpdateCounter struct { *akara.BaseSystem *d2util.Logger @@ -29,6 +32,7 @@ type UpdateCounter struct { count int } +// Init initializes the update counter func (u *UpdateCounter) Init(world *akara.World) { u.World = world @@ -39,6 +43,7 @@ func (u *UpdateCounter) Init(world *akara.World) { u.Info("initializing") } +// Update the world update count in 1 second intervals func (u *UpdateCounter) Update() { u.count++ u.secondsElapsed += u.World.TimeDelta.Seconds() @@ -51,4 +56,3 @@ func (u *UpdateCounter) Update() { u.secondsElapsed = 0 u.count = 0 } - diff --git a/main.go b/main.go index 1aa178fd..07f32749 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,10 @@ package main import ( "log" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2systems" + "github.com/gravestench/akara" + "gopkg.in/alecthomas/kingpin.v2" + "github.com/OpenDiablo2/OpenDiablo2/d2app" ) @@ -15,6 +19,21 @@ var GitBranch string var GitCommit string func main() { + ecs := kingpin.Flag("ecs", "start the ecs implementation").Bool() + kingpin.Parse() + + if *ecs { + cfg := akara.NewWorldConfig() + + cfg. + With(d2systems.NewAppBootstrapSystem()). + With(d2systems.NewGameClientBootstrapSystem()) + + akara.NewWorld(cfg) + + return + } + log.SetFlags(log.Lshortfile) log.Println("OpenDiablo2 - Open source Diablo 2 engine")