1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-03 23:26:41 -05:00

more work on ecs impl

* added command line arg for launching ecs impl
* removed render system tests, was causing gl context issues in tests
* fixed all lint errors in d2systems
This commit is contained in:
gravestench 2020-11-22 01:37:55 -08:00
parent 069201a980
commit caafe7592c
26 changed files with 355 additions and 840 deletions

View File

@ -97,4 +97,3 @@ func (cm *AnimationMap) Remove(id akara.EID) {
delete(cm.components, id)
cm.world.UpdateEntity(id)
}

View File

@ -0,0 +1,2 @@
// Package d2components provides all of the ECS components
package d2components

View File

@ -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

View File

@ -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

2
d2core/d2systems/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package d2systems provides all of the ECS systems
package d2systems

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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 ...
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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"

View File

@ -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()
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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())
//}

View File

@ -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()

View File

@ -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
}

View File

@ -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")

View File

@ -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
}

19
main.go
View File

@ -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")