diff --git a/d2core/d2components/color.go b/d2core/d2components/color.go new file mode 100644 index 00000000..e902c1c1 --- /dev/null +++ b/d2core/d2components/color.go @@ -0,0 +1,44 @@ +//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same +package d2components + +import ( + "image/color" + + "github.com/gravestench/akara" +) + +// static check that Color implements Component +var _ akara.Component = &Color{} + +// Color is a flag component that is used to denote a "dirty" state +type Color struct { + color.Color +} + +// New creates a new Color. By default, IsColor is false. +func (*Color) New() akara.Component { + return &Color{ + color.Transparent, + } +} + +// ColorFactory is a wrapper for the generic component factory that returns Color component instances. +// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Color. +type ColorFactory struct { + Color *akara.ComponentFactory +} + +// AddColor adds a Color component to the given entity and returns it +func (m *ColorFactory) AddColor(id akara.EID) *Color { + return m.Color.Add(id).(*Color) +} + +// GetColor returns the Color component for the given entity, and a bool for whether or not it exists +func (m *ColorFactory) GetColor(id akara.EID) (*Color, bool) { + component, found := m.Color.Get(id) + if !found { + return nil, found + } + + return component.(*Color), found +} diff --git a/d2core/d2systems/app_bootstrap.go b/d2core/d2systems/app_bootstrap.go index 55142cef..c0498060 100644 --- a/d2core/d2systems/app_bootstrap.go +++ b/d2core/d2systems/app_bootstrap.go @@ -94,17 +94,11 @@ func (m *AppBootstrapSystem) setupSubscriptions() { func (m *AppBootstrapSystem) setupFactories() { m.Info("setting up component factories") - gameConfigID := m.RegisterComponent(&d2components.GameConfig{}) - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - fileHandleID := m.RegisterComponent(&d2components.FileHandle{}) - fileSourceID := m.RegisterComponent(&d2components.FileSource{}) - - m.GameConfig = m.GetComponentFactory(gameConfigID) - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) - m.FileHandle = m.GetComponentFactory(fileHandleID) - m.FileSource = m.GetComponentFactory(fileSourceID) + m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) + m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle) + m.InjectComponent(&d2components.FileSource{}, &m.FileSource) } func (m *AppBootstrapSystem) injectSystems() { diff --git a/d2core/d2systems/asset_loader.go b/d2core/d2systems/asset_loader.go index d6393d4c..441ef90e 100644 --- a/d2core/d2systems/asset_loader.go +++ b/d2core/d2systems/asset_loader.go @@ -115,39 +115,22 @@ func (m *AssetLoaderSystem) setupSubscriptions() { func (m *AssetLoaderSystem) setupFactories() { m.Info("setting up component factories") - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - fileHandleID := m.RegisterComponent(&d2components.FileHandle{}) - fileSourceID := m.RegisterComponent(&d2components.FileSource{}) - stringTableID := m.RegisterComponent(&d2components.StringTable{}) - fontTableID := m.RegisterComponent(&d2components.FontTable{}) - dataDictionaryID := m.RegisterComponent(&d2components.DataDictionary{}) - paletteID := m.RegisterComponent(&d2components.Palette{}) - paletteTransformID := m.RegisterComponent(&d2components.PaletteTransform{}) - cofID := m.RegisterComponent(&d2components.Cof{}) - dc6ID := m.RegisterComponent(&d2components.Dc6{}) - dccID := m.RegisterComponent(&d2components.Dcc{}) - ds1ID := m.RegisterComponent(&d2components.Ds1{}) - dt1ID := m.RegisterComponent(&d2components.Dt1{}) - wavID := m.RegisterComponent(&d2components.Wav{}) - animationDataID := m.RegisterComponent(&d2components.AnimationData{}) - - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) - m.FileHandle = m.GetComponentFactory(fileHandleID) - m.FileSource = m.GetComponentFactory(fileSourceID) - m.StringTable = m.GetComponentFactory(stringTableID) - m.DataDictionary = m.GetComponentFactory(dataDictionaryID) - m.Palette = m.GetComponentFactory(paletteID) - m.PaletteTransform = m.GetComponentFactory(paletteTransformID) - m.FontTable = m.GetComponentFactory(fontTableID) - m.Cof = m.GetComponentFactory(cofID) - m.Dc6 = m.GetComponentFactory(dc6ID) - m.Dcc = m.GetComponentFactory(dccID) - m.Ds1 = m.GetComponentFactory(ds1ID) - m.Dt1 = m.GetComponentFactory(dt1ID) - m.Wav = m.GetComponentFactory(wavID) - m.AnimationData = m.GetComponentFactory(animationDataID) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) + m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle) + m.InjectComponent(&d2components.FileSource{}, &m.FileSource) + m.InjectComponent(&d2components.StringTable{}, &m.StringTable) + m.InjectComponent(&d2components.FontTable{}, &m.FontTable) + m.InjectComponent(&d2components.DataDictionary{}, &m.DataDictionary) + m.InjectComponent(&d2components.Palette{}, &m.Palette) + m.InjectComponent(&d2components.PaletteTransform{}, &m.PaletteTransform) + m.InjectComponent(&d2components.Cof{}, &m.Cof) + m.InjectComponent(&d2components.Dc6{}, &m.Dc6) + m.InjectComponent(&d2components.Dcc{}, &m.Dcc) + m.InjectComponent(&d2components.Ds1{}, &m.Ds1) + m.InjectComponent(&d2components.Dt1{}, &m.Dt1) + m.InjectComponent(&d2components.Wav{}, &m.Wav) + m.InjectComponent(&d2components.AnimationData{}, &m.AnimationData) } // Update processes all of the Entities in the subscription of file entities that need to be processed diff --git a/d2core/d2systems/doc.go b/d2core/d2systems/doc.go index 601c8e09..8c46db19 100644 --- a/d2core/d2systems/doc.go +++ b/d2core/d2systems/doc.go @@ -1,2 +1,2 @@ -// Package d2systems provides all of the ECS systems +// Package d2systems provides all of the ECS baseSystems package d2systems diff --git a/d2core/d2systems/file_handle_resolver.go b/d2core/d2systems/file_handle_resolver.go index 3c33d6db..18b25981 100644 --- a/d2core/d2systems/file_handle_resolver.go +++ b/d2core/d2systems/file_handle_resolver.go @@ -95,15 +95,10 @@ func (m *FileHandleResolver) setupSubscriptions() { func (m *FileHandleResolver) setupFactories() { m.Info("setting up component factories") - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - fileHandleID := m.RegisterComponent(&d2components.FileHandle{}) - fileSourceID := m.RegisterComponent(&d2components.FileSource{}) - - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) - m.FileHandle = m.GetComponentFactory(fileHandleID) - m.FileSource = m.GetComponentFactory(fileSourceID) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) + m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle) + m.InjectComponent(&d2components.FileSource{}, &m.FileSource) } // Update iterates over entities which have not had a file handle resolved. diff --git a/d2core/d2systems/file_source_resolver.go b/d2core/d2systems/file_source_resolver.go index ee2358c0..9862339d 100644 --- a/d2core/d2systems/file_source_resolver.go +++ b/d2core/d2systems/file_source_resolver.go @@ -70,13 +70,9 @@ func (m *FileSourceResolver) setupSubscriptions() { func (m *FileSourceResolver) setupFactories() { m.Info("setting up component factories") - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - fileSourceID := m.RegisterComponent(&d2components.FileSource{}) - - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) - m.FileSource = m.GetComponentFactory(fileSourceID) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) + m.InjectComponent(&d2components.FileSource{}, &m.FileSource) } // Update iterates over entities from its subscription, and checks if it can be used as a file source diff --git a/d2core/d2systems/file_type_resolver.go b/d2core/d2systems/file_type_resolver.go index 98d6b950..d1c46dbe 100644 --- a/d2core/d2systems/file_type_resolver.go +++ b/d2core/d2systems/file_type_resolver.go @@ -52,11 +52,8 @@ func (m *FileTypeResolver) setupLogger() { } func (m *FileTypeResolver) setupFactories() { - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) } func (m *FileTypeResolver) setupSubscriptions() { diff --git a/d2core/d2systems/game_client_bootstrap.go b/d2core/d2systems/game_client_bootstrap.go index b163a45f..b5aaf050 100644 --- a/d2core/d2systems/game_client_bootstrap.go +++ b/d2core/d2systems/game_client_bootstrap.go @@ -14,13 +14,13 @@ const ( var _ akara.System = &GameClientBootstrapSystem{} // GameClientBootstrapSystem is responsible for setting up other -// systems that are common to both the game client and the headless game server +// baseSystems that are common to both the game client and the headless game server type GameClientBootstrapSystem struct { akara.BaseSubscriberSystem *d2util.Logger } -// Init injects the common systems required by both the game client and headless server +// Init injects the common baseSystems required by both the game client and headless server func (m *GameClientBootstrapSystem) Init(world *akara.World) { m.World = world diff --git a/d2core/d2systems/game_config.go b/d2core/d2systems/game_config.go index f4ed1b4f..71e01825 100644 --- a/d2core/d2systems/game_config.go +++ b/d2core/d2systems/game_config.go @@ -24,9 +24,9 @@ const ( // to be found in, and it also adds an entity for the initial config file to be loaded. // // This system is dependant on the FileTypeResolver, FileSourceResolver, and -// FileHandleResolver systems because this system subscribes to entities -// with components created by these other systems. Nothing will break if these -// other systems are not present in the world, but no config files will be loaded by +// FileHandleResolver baseSystems because this system subscribes to entities +// with components created by these other baseSystems. Nothing will break if these +// other baseSystems are not present in the world, but no config files will be loaded by // this system either... type GameConfigSystem struct { akara.BaseSubscriberSystem @@ -62,19 +62,12 @@ func (m *GameConfigSystem) setupLogger() { func (m *GameConfigSystem) setupFactories() { m.Info("setting up component factories") - filePathID := m.RegisterComponent(&d2components.FilePath{}) - fileTypeID := m.RegisterComponent(&d2components.FileType{}) - fileHandleID := m.RegisterComponent(&d2components.FileHandle{}) - fileSourceID := m.RegisterComponent(&d2components.FileSource{}) - gameConfigID := m.RegisterComponent(&d2components.GameConfig{}) - dirtyID := m.RegisterComponent(&d2components.Dirty{}) - - m.FilePath = m.GetComponentFactory(filePathID) - m.FileType = m.GetComponentFactory(fileTypeID) - m.FileHandle = m.GetComponentFactory(fileHandleID) - m.FileSource = m.GetComponentFactory(fileSourceID) - m.GameConfig = m.GetComponentFactory(gameConfigID) - m.Dirty = m.GetComponentFactory(dirtyID) + m.InjectComponent(&d2components.FilePath{}, &m.FilePath) + m.InjectComponent(&d2components.FileType{}, &m.FileType) + m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle) + m.InjectComponent(&d2components.FileSource{}, &m.FileSource) + m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig) + m.InjectComponent(&d2components.Dirty{}, &m.Dirty) } func (m *GameConfigSystem) setupSubscriptions() { diff --git a/d2core/d2systems/game_config_test.go b/d2core/d2systems/game_config_test.go index 82466e02..f3e2953c 100644 --- a/d2core/d2systems/game_config_test.go +++ b/d2core/d2systems/game_config_test.go @@ -26,7 +26,7 @@ func Test_GameConfigSystem_Bootstrap(t *testing.T) { cfgSys.AddFilePath(world.NewEntity()).Path = testDataPath cfgSys.AddFilePath(world.NewEntity()).Path = "config.json" - // at this point the world has initialized the systems. when the world + // at this point the world has initialized the baseSystems. when the world // updates it should process the config dir to a source and then // use the source to resolve a file handle, and finally the config file // will get loaded by the config system. diff --git a/d2core/d2systems/game_object_factory.go b/d2core/d2systems/game_object_factory.go index 1f5c5294..ff906f2d 100644 --- a/d2core/d2systems/game_object_factory.go +++ b/d2core/d2systems/game_object_factory.go @@ -13,12 +13,13 @@ const ( // static check that GameObjectFactory implements the System interface var _ akara.System = &GameObjectFactory{} -// GameObjectFactory is a wrapper system for subordinate systems that +// GameObjectFactory is a wrapper system for subordinate baseSystems that // do the actual object creation work. type GameObjectFactory struct { akara.BaseSystem *d2util.Logger *SpriteFactory + *ShapeSystem } // Init will initialize the Game Object Factory by injecting all of the factory subsystems into the world @@ -40,9 +41,11 @@ func (t *GameObjectFactory) setupLogger() { func (t *GameObjectFactory) injectSubSystems() { t.Info("creating sprite factory") t.SpriteFactory = NewSpriteFactorySubsystem(t.BaseSystem, t.Logger) + t.ShapeSystem = NewShapeSystem(t.BaseSystem, t.Logger) } -// Update updates all the sub-systems +// Update updates all the sub-baseSystems func (t *GameObjectFactory) Update() { t.SpriteFactory.Update() + t.ShapeSystem.Update() } diff --git a/d2core/d2systems/input_system.go b/d2core/d2systems/input_system.go index 00e83d9e..dd4aad19 100644 --- a/d2core/d2systems/input_system.go +++ b/d2core/d2systems/input_system.go @@ -22,7 +22,7 @@ var _ akara.System = &InputSystem{} type InputSystem struct { akara.BaseSubscriberSystem *d2util.Logger - inputService d2interface.InputService + d2interface.InputService configs *akara.Subscription interactives *akara.Subscription d2components.GameConfigFactory @@ -34,7 +34,7 @@ type InputSystem struct { func (m *InputSystem) Init(world *akara.World) { m.World = world - m.inputService = ebiten_input.InputService{} + m.InputService = ebiten_input.InputService{} m.setupLogger() @@ -54,11 +54,8 @@ func (m *InputSystem) setupLogger() { func (m *InputSystem) setupFactories() { m.Info("setting up component factories") - gameConfigID := m.RegisterComponent(&d2components.GameConfig{}) - interactiveID := m.RegisterComponent(&d2components.Interactive{}) - - m.GameConfig = m.GetComponentFactory(gameConfigID) - m.Interactive = m.GetComponentFactory(interactiveID) + m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig) + m.InjectComponent(&d2components.Interactive{}, &m.Interactive) } func (m *InputSystem) setupSubscriptions() { @@ -121,17 +118,17 @@ func (m *InputSystem) updateInputState() { } for _, key := range keysToCheck { - truth := m.inputService.IsKeyPressed(d2enum.Key(key)) + truth := m.InputService.IsKeyPressed(d2enum.Key(key)) m.inputState.KeyVector.Set(key, truth) } for _, mod := range modifiersToCheck { - truth := m.inputService.IsKeyPressed(d2enum.Key(mod)) + truth := m.InputService.IsKeyPressed(d2enum.Key(mod)) m.inputState.ModifierVector.Set(mod, truth) } for _, btn := range buttonsToCheck { - truth := m.inputService.IsMouseButtonPressed(d2enum.MouseButton(btn)) + truth := m.InputService.IsMouseButtonPressed(d2enum.MouseButton(btn)) m.inputState.MouseButtonVector.Set(btn, truth) } } diff --git a/d2core/d2systems/movement.go b/d2core/d2systems/movement.go index 605b55b4..f558b10f 100644 --- a/d2core/d2systems/movement.go +++ b/d2core/d2systems/movement.go @@ -35,11 +35,8 @@ func (m *MovementSystem) Init(world *akara.World) { m.Info("initializing ...") - positionID := m.RegisterComponent(&d2components.Position{}) - velocityID := m.RegisterComponent(&d2components.Velocity{}) - - m.Position = m.GetComponentFactory(positionID) - m.Velocity = m.GetComponentFactory(velocityID) + m.InjectComponent(&d2components.Position{}, &m.Position) + m.InjectComponent(&d2components.Velocity{}, &m.Velocity) movable := m.NewComponentFilter().Require( &d2components.Position{}, diff --git a/d2core/d2systems/movement_test.go b/d2core/d2systems/movement_test.go index d5f55cd3..85e21816 100644 --- a/d2core/d2systems/movement_test.go +++ b/d2core/d2systems/movement_test.go @@ -51,8 +51,8 @@ func TestMovementSystem_EntityAdded(t *testing.T) { px, py := 10., 10. vx, vy := 1., 0. - position.Set(px, py) - velocity.Set(vx, vy) + position.X, position.Y = px, py + velocity.X, velocity.Y = vx, vy if len(moveSys.movableEntities.GetEntities()) != 1 { t.Error("entity not added to the system") @@ -60,16 +60,16 @@ func TestMovementSystem_EntityAdded(t *testing.T) { if p, found := moveSys.GetPosition(e); !found { t.Error("position component not found") - } else if p.X() != px || p.Y() != py { + } else if p.X != px || p.Y != py { fmtError := "position component values incorrect:\n\t expected %v, %v but got %v, %v" - t.Errorf(fmtError, px, py, p.X(), p.Y()) + t.Errorf(fmtError, px, py, p.X, p.Y) } if v, found := moveSys.GetVelocity(e); !found { t.Error("position component not found") - } else if v.X() != vx || v.Y() != vy { + } else if v.X != vx || v.Y != vy { fmtError := "velocity component values incorrect:\n\t expected %v, %v but got %v, %v" - t.Errorf(fmtError, px, py, v.X(), v.Y()) + t.Errorf(fmtError, px, py, v.X, v.Y) } } @@ -91,15 +91,15 @@ func TestMovementSystem_Update(t *testing.T) { vx, vy := 1., -1. // mutate the components a bit - position.Set(px, py) - velocity.Set(vx, vy) + position.X, position.Y = px, py + velocity.X, velocity.Y = vx, vy // should apply the velocity to the position _ = world.Update(time.Second) - if position.X() != px+vx || position.Y() != py+vy { + if position.X != px+vx || position.Y != py+vy { fmtError := "expected position (%v, %v) but got (%v, %v)" - t.Errorf(fmtError, px+vx, py+vy, position.X(), position.Y()) + t.Errorf(fmtError, px+vx, py+vy, position.X, position.Y) } } @@ -117,8 +117,8 @@ func benchN(n int, b *testing.B) { p := movementSystem.AddPosition(e) v := movementSystem.AddVelocity(e) - p.Set(0, 0) - v.Set(rand.Float64(), rand.Float64()) //nolint:gosec // it's just a test + p.X, p.Y = 0, 0 + v.X, v.Y = rand.Float64(), rand.Float64() //nolint:gosec // it's just a test } benchName := strconv.Itoa(n) + "_entity update" diff --git a/d2core/d2systems/render.go b/d2core/d2systems/render.go index 10aeeb39..25a05192 100644 --- a/d2core/d2systems/render.go +++ b/d2core/d2systems/render.go @@ -63,19 +63,12 @@ func (m *RenderSystem) setupLogger() { } func (m *RenderSystem) setupFactories() { - gameConfigID := m.RegisterComponent(&d2components.GameConfig{}) - viewportID := m.RegisterComponent(&d2components.Viewport{}) - mainViewportID := m.RegisterComponent(&d2components.MainViewport{}) - renderableID := m.RegisterComponent(&d2components.Texture{}) - priorityID := m.RegisterComponent(&d2components.Priority{}) - alphaID := m.RegisterComponent(&d2components.Alpha{}) - - m.GameConfig = m.GetComponentFactory(gameConfigID) - m.Viewport = m.GetComponentFactory(viewportID) - m.MainViewport = m.GetComponentFactory(mainViewportID) - m.Texture = m.GetComponentFactory(renderableID) - m.Priority = m.GetComponentFactory(priorityID) - m.Alpha = m.GetComponentFactory(alphaID) + m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig) + m.InjectComponent(&d2components.Viewport{}, &m.Viewport) + m.InjectComponent(&d2components.MainViewport{}, &m.MainViewport) + m.InjectComponent(&d2components.Texture{}, &m.Texture) + m.InjectComponent(&d2components.Priority{}, &m.Priority) + m.InjectComponent(&d2components.Alpha{}, &m.Alpha) } func (m *RenderSystem) setupSubscriptions() { @@ -98,7 +91,7 @@ func (m *RenderSystem) setupSubscriptions() { // 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 +// The reason why this isn't in the init step is because we use other baseSystems // for loading the config file, and it may take more than one iteration func (m *RenderSystem) Update() { if m.renderer != nil { diff --git a/d2core/d2systems/scene_base.go b/d2core/d2systems/scene_base.go index c19a2d02..dd7a8d62 100644 --- a/d2core/d2systems/scene_base.go +++ b/d2core/d2systems/scene_base.go @@ -4,6 +4,8 @@ import ( "fmt" "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2scene" "github.com/gravestench/akara" @@ -27,7 +29,7 @@ func NewBaseScene(key string) *BaseScene { key: key, Viewports: make([]akara.EID, 0), GameObjects: make([]akara.EID, 0), - systems: &baseSystems{}, + baseSystems: &baseSystems{}, backgroundColor: color.Transparent, } @@ -44,18 +46,21 @@ type baseSystems struct { *GameObjectFactory } -// BaseScene encapsulates common behaviors for systems that are considered "scenes", +// BaseScene encapsulates common behaviors for baseSystems 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 + *baseSystems + Geom struct { + Rectangle rectangle.Namespace + } *d2util.Logger key string booted bool paused bool - systems *baseSystems Add *sceneObjectFactory Viewports []akara.EID GameObjects []akara.EID @@ -75,6 +80,8 @@ type BaseScene struct { d2components.OriginFactory d2components.AlphaFactory d2components.DrawEffectFactory + d2components.RectangleFactory + d2components.ColorFactory } // Booted returns whether or not the scene has booted @@ -100,8 +107,6 @@ func (s *BaseScene) Init(world *akara.World) { func (s *BaseScene) boot() { s.Info("base scene booting ...") - s.setupFactories() - s.Add = &sceneObjectFactory{ BaseScene: s, Logger: d2util.NewLogger(), @@ -110,45 +115,52 @@ func (s *BaseScene) boot() { s.Add.SetPrefix(fmt.Sprintf("%s -> %s", s.key, "Object Factory")) for idx := range s.Systems { - if rendersys, ok := s.Systems[idx].(*RenderSystem); ok && s.systems.RenderSystem == nil { - s.systems.RenderSystem = rendersys + if rendersys, ok := s.Systems[idx].(*RenderSystem); ok && s.baseSystems.RenderSystem == nil { + s.baseSystems.RenderSystem = rendersys continue } - if inputSys, ok := s.Systems[idx].(*InputSystem); ok && s.systems.InputSystem == nil { - s.systems.InputSystem = inputSys + if inputSys, ok := s.Systems[idx].(*InputSystem); ok && s.baseSystems.InputSystem == nil { + s.baseSystems.InputSystem = inputSys continue } - if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok && s.systems.GameObjectFactory == nil { - s.systems.GameObjectFactory = objFactory + if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok && s.baseSystems.GameObjectFactory == nil { + s.baseSystems.GameObjectFactory = objFactory continue } } - if s.systems.RenderSystem == nil { + if s.baseSystems.RenderSystem == nil { s.Info("waiting for render system ...") return } - if s.systems.RenderSystem.renderer == nil { + if s.baseSystems.RenderSystem.renderer == nil { s.Info("waiting for renderer instance ...") return } - if s.systems.InputSystem == nil { + if s.baseSystems.InputSystem == nil { s.Info("waiting for input system") return } - if s.systems.GameObjectFactory == nil { + if s.baseSystems.GameObjectFactory == nil { s.Info("waiting for game object factory ...") return } - s.systems.SpriteFactory.RenderSystem = s.systems.RenderSystem + s.setupFactories() - s.createDefaultViewport() + s.baseSystems.SpriteFactory.RenderSystem = s.baseSystems.RenderSystem + + const ( + defaultWidth = 800 + defaultHeight = 600 + ) + + s.Add.Viewport(mainViewport, defaultWidth, defaultHeight) s.Info("base scene booted!") s.booted = true @@ -157,56 +169,22 @@ func (s *BaseScene) boot() { func (s *BaseScene) setupFactories() { s.Info("setting up component factories") - mainViewportID := s.RegisterComponent(&d2components.MainViewport{}) - viewportID := s.RegisterComponent(&d2components.Viewport{}) - viewportFilterID := s.RegisterComponent(&d2components.ViewportFilter{}) - cameraID := s.RegisterComponent(&d2components.Camera{}) - priorityID := s.RegisterComponent(&d2components.Priority{}) - renderableID := s.RegisterComponent(&d2components.Texture{}) - interactiveID := s.RegisterComponent(&d2components.Interactive{}) - positionID := s.RegisterComponent(&d2components.Position{}) - scaleID := s.RegisterComponent(&d2components.Scale{}) - animationID := s.RegisterComponent(&d2components.Sprite{}) - originID := s.RegisterComponent(&d2components.Origin{}) - alphaID := s.RegisterComponent(&d2components.Alpha{}) - sceneGraphNodeID := s.RegisterComponent(&d2components.SceneGraphNode{}) - drawEffectID := s.RegisterComponent(&d2components.DrawEffect{}) - - s.MainViewport = s.GetComponentFactory(mainViewportID) - s.Viewport = s.GetComponentFactory(viewportID) - s.ViewportFilter = s.GetComponentFactory(viewportFilterID) - s.Camera = s.GetComponentFactory(cameraID) - s.Priority = s.GetComponentFactory(priorityID) - s.Texture = s.GetComponentFactory(renderableID) - s.Interactive = s.GetComponentFactory(interactiveID) - s.Position = s.GetComponentFactory(positionID) - s.Scale = s.GetComponentFactory(scaleID) - s.Sprite = s.GetComponentFactory(animationID) - s.Origin = s.GetComponentFactory(originID) - s.Alpha = s.GetComponentFactory(alphaID) - s.SceneGraphNode = s.GetComponentFactory(sceneGraphNodeID) - s.DrawEffect = s.GetComponentFactory(drawEffectID) -} - -func (s *BaseScene) createDefaultViewport() { - s.Info("creating default viewport") - viewportID := s.NewEntity() - s.AddViewport(viewportID) - s.AddPriority(viewportID) - - camera := s.AddCamera(viewportID) - width, height := camera.Size.XY() - - s.AddSceneGraphNode(viewportID).SetParent(s.Graph) - - sfc := s.systems.RenderSystem.renderer.NewSurface(int(width), int(height)) - - sfc.Clear(color.Transparent) - - s.AddTexture(viewportID).Texture = sfc - s.AddMainViewport(viewportID) - - s.Viewports = append(s.Viewports, viewportID) + s.InjectComponent(&d2components.MainViewport{}, &s.MainViewport) + s.InjectComponent(&d2components.Viewport{}, &s.Viewport) + s.InjectComponent(&d2components.ViewportFilter{}, &s.ViewportFilter) + s.InjectComponent(&d2components.Camera{}, &s.Camera) + s.InjectComponent(&d2components.Priority{}, &s.Priority) + s.InjectComponent(&d2components.Texture{}, &s.Texture) + s.InjectComponent(&d2components.Interactive{}, &s.Interactive) + s.InjectComponent(&d2components.Position{}, &s.Position) + s.InjectComponent(&d2components.Scale{}, &s.Scale) + s.InjectComponent(&d2components.Origin{}, &s.Origin) + s.InjectComponent(&d2components.Alpha{}, &s.Alpha) + s.InjectComponent(&d2components.SceneGraphNode{}, &s.SceneGraphNode) + s.InjectComponent(&d2components.DrawEffect{}, &s.DrawEffect) + s.InjectComponent(&d2components.Sprite{}, &s.SpriteFactory.Sprite) + s.InjectComponent(&d2components.Rectangle{}, &s.RectangleFactory.Rectangle) + s.InjectComponent(&d2components.Color{}, &s.Color) } // Key returns the scene's key @@ -229,12 +207,12 @@ func (s *BaseScene) Update() { } func (s *BaseScene) renderViewports() { - if s.systems.RenderSystem == nil { + if s.baseSystems.RenderSystem == nil { s.Warning("render system not present") return } - if s.systems.RenderSystem.renderer == nil { + if s.baseSystems.RenderSystem.renderer == nil { s.Warning("render system doesn't have a renderer instance") return } @@ -242,7 +220,8 @@ func (s *BaseScene) renderViewports() { numViewports := len(s.Viewports) if numViewports < 1 { - s.createDefaultViewport() + s.Warning("scene does not have a main viewport") + return } viewportObjects := s.binGameObjectsByViewport() @@ -307,7 +286,7 @@ func (s *BaseScene) renderViewport(idx int, objects []akara.EID) { } if sfc.Texture == nil { - sfc.Texture = s.systems.RenderSystem.renderer.NewSurface(int(cw), int(ch)) + sfc.Texture = s.baseSystems.RenderSystem.renderer.NewSurface(int(cw), int(ch)) } if idx == mainViewport { @@ -378,41 +357,44 @@ func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) { target.PushColor(color.Alpha{A: uint8(alpha.Alpha * maxAlpha)}) defer target.Pop() - segment, found := s.systems.SpriteFactory.GetSegmentedSprite(id) + segment, found := s.baseSystems.SpriteFactory.GetSegmentedSprite(id) if found { - animation, found := s.GetSprite(id) - if !found { - return - } - - var offsetY int - - segmentsX, segmentsY := segment.Xsegments, segment.Ysegments - frameOffset := segment.FrameOffset - - for y := 0; y < segmentsY; y++ { - var offsetX, maxFrameHeight int - - for x := 0; x < segmentsX; x++ { - idx := x + y*segmentsX + frameOffset*segmentsX*segmentsY - if err := animation.SetCurrentFrame(idx); err != nil { - s.Error("SetCurrentFrame error" + err.Error()) - } - - target.PushTranslation(x+offsetX, y+offsetY) - target.Render(animation.GetCurrentFrameSurface()) - target.Pop() - - frameWidth, frameHeight := animation.GetCurrentFrameSize() - maxFrameHeight = d2math.MaxInt(maxFrameHeight, frameHeight) - offsetX += frameWidth - 1 - } - - offsetY += maxFrameHeight - 1 - } - + s.renderSegmentedSprite(target, id, segment) return } target.Render(texture.Texture) } + +func (s *BaseScene) renderSegmentedSprite(target d2interface.Surface, id akara.EID, seg *d2components.SegmentedSprite) { + animation, found := s.GetSprite(id) + if !found { + return + } + + var offsetY int + + segmentsX, segmentsY := seg.Xsegments, seg.Ysegments + frameOffset := seg.FrameOffset + + for y := 0; y < segmentsY; y++ { + var offsetX, maxFrameHeight int + + for x := 0; x < segmentsX; x++ { + idx := x + y*segmentsX + frameOffset*segmentsX*segmentsY + if err := animation.SetCurrentFrame(idx); err != nil { + s.Error("SetCurrentFrame error" + err.Error()) + } + + target.PushTranslation(x+offsetX, y+offsetY) + target.Render(animation.GetCurrentFrameSurface()) + target.Pop() + + frameWidth, frameHeight := animation.GetCurrentFrameSize() + maxFrameHeight = d2math.MaxInt(maxFrameHeight, frameHeight) + offsetX += frameWidth - 1 + } + + offsetY += maxFrameHeight - 1 + } +} diff --git a/d2core/d2systems/scene_loading_screen.go b/d2core/d2systems/scene_loading_screen.go index fecc1e2e..657b8fd7 100644 --- a/d2core/d2systems/scene_loading_screen.go +++ b/d2core/d2systems/scene_loading_screen.go @@ -32,9 +32,14 @@ func NewLoadingScene() *LoadingScene { type LoadingScene struct { *BaseScene loadingSprite akara.EID - filesToLoad *akara.Subscription - d2components.TextureFactory - booted bool + loadStages struct { + stage1 *akara.Subscription // has path, no type + stage2 *akara.Subscription // has type, no handle + stage3 *akara.Subscription // has handle, no asset + stage4 *akara.Subscription // is loaded + } + progress float64 + booted bool } // Init the loading scene @@ -45,20 +50,62 @@ func (s *LoadingScene) Init(world *akara.World) { s.backgroundColor = color.Black - s.setupFactories() s.setupSubscriptions() } -func (s *LoadingScene) setupFactories() { - renderableID := s.RegisterComponent(&d2components.Texture{}) - s.Texture = s.GetComponentFactory(renderableID) -} - func (s *LoadingScene) setupSubscriptions() { s.Info("setting up component subscriptions") - filesToLoad := s.NewComponentFilter(). + stage1 := s.NewComponentFilter(). Require( + &d2components.FilePath{}, + ). + Forbid( // but we forbid files that are already loaded + &d2components.FileType{}, + &d2components.FileHandle{}, + &d2components.FileSource{}, + &d2components.GameConfig{}, + &d2components.StringTable{}, + &d2components.DataDictionary{}, + &d2components.Palette{}, + &d2components.PaletteTransform{}, + &d2components.Cof{}, + &d2components.Dc6{}, + &d2components.Dcc{}, + &d2components.Ds1{}, + &d2components.Dt1{}, + &d2components.Wav{}, + &d2components.AnimationData{}, + ). + Build() + + stage2 := s.NewComponentFilter(). + Require( + &d2components.FilePath{}, + &d2components.FileType{}, + ). + Forbid( // but we forbid files that are already loaded + &d2components.FileHandle{}, + &d2components.FileSource{}, + &d2components.GameConfig{}, + &d2components.StringTable{}, + &d2components.DataDictionary{}, + &d2components.Palette{}, + &d2components.PaletteTransform{}, + &d2components.Cof{}, + &d2components.Dc6{}, + &d2components.Dcc{}, + &d2components.Ds1{}, + &d2components.Dt1{}, + &d2components.Wav{}, + &d2components.AnimationData{}, + ). + Build() + + stage3 := s.NewComponentFilter(). + Require( + &d2components.FilePath{}, + &d2components.FileType{}, &d2components.FileHandle{}, ). Forbid( // but we forbid files that are already loaded @@ -78,7 +125,30 @@ func (s *LoadingScene) setupSubscriptions() { ). Build() - s.filesToLoad = s.World.AddSubscription(filesToLoad) + // we want to know about loaded files, too + stage4 := s.NewComponentFilter(). + RequireOne( + &d2components.FileHandle{}, + &d2components.FileSource{}, + &d2components.GameConfig{}, + &d2components.StringTable{}, + &d2components.DataDictionary{}, + &d2components.Palette{}, + &d2components.PaletteTransform{}, + &d2components.Cof{}, + &d2components.Dc6{}, + &d2components.Dcc{}, + &d2components.Ds1{}, + &d2components.Dt1{}, + &d2components.Wav{}, + &d2components.AnimationData{}, + ). + Build() + + s.loadStages.stage1 = s.World.AddSubscription(stage1) // has path, no type + s.loadStages.stage2 = s.World.AddSubscription(stage2) // has type, no handle + s.loadStages.stage3 = s.World.AddSubscription(stage3) // has handle, no asset + s.loadStages.stage4 = s.World.AddSubscription(stage4) // is loaded } func (s *LoadingScene) boot() { @@ -111,6 +181,7 @@ func (s *LoadingScene) Update() { s.boot() } + s.updateLoadProgress() s.updateViewportAlpha() s.updateLoadingSpritePosition() s.updateLoadingSpriteFrame() @@ -118,6 +189,16 @@ func (s *LoadingScene) Update() { s.BaseScene.Update() } +func (s *LoadingScene) updateLoadProgress() { + untyped := float64(len(s.loadStages.stage1.GetEntities())) + unhandled := float64(len(s.loadStages.stage2.GetEntities())) + unparsed := float64(len(s.loadStages.stage3.GetEntities())) + loaded := float64(len(s.loadStages.stage4.GetEntities())) + + s.progress = 1 - ((untyped + unhandled + unparsed) / 3 / loaded) + _ = s.progress +} + func (s *LoadingScene) updateViewportAlpha() { if len(s.Viewports) < 1 { return @@ -128,12 +209,14 @@ func (s *LoadingScene) updateViewportAlpha() { return } - isLoading := len(s.filesToLoad.GetEntities()) > 0 + isLoading := len(s.loadStages.stage1.GetEntities()) > 0 || + len(s.loadStages.stage2.GetEntities()) > 0 || + len(s.loadStages.stage3.GetEntities()) > 0 if isLoading { - alpha.Alpha = math.Min(alpha.Alpha+0.1, 1) + alpha.Alpha = math.Min(alpha.Alpha+0.125, 1) } else { - alpha.Alpha = math.Max(alpha.Alpha-0.1, 0) + alpha.Alpha = math.Max(alpha.Alpha-0.125, 0) } } @@ -165,9 +248,13 @@ func (s *LoadingScene) updateLoadingSpritePosition() { } func (s *LoadingScene) updateLoadingSpriteFrame() { - //sprite, found := s.GetSprite(s.loadingSprite) - //if !found { - // return - //} + sprite, found := s.GetSprite(s.loadingSprite) + if !found { + return + } + numFrames := float64(sprite.GetFrameCount()) + if err := sprite.SetCurrentFrame(int(s.progress * (numFrames - 1))); err != nil { + _ = sprite.SetCurrentFrame(0) + } } diff --git a/d2core/d2systems/scene_main_menu.go b/d2core/d2systems/scene_main_menu.go index 3cf245a2..447a14b6 100644 --- a/d2core/d2systems/scene_main_menu.go +++ b/d2core/d2systems/scene_main_menu.go @@ -15,6 +15,12 @@ const ( sceneKeyMainMenu = "Main Menu" ) +const ( + viewportMainBackground = iota + 1 + viewportTrademark + viewport +) + // NewMainMenuScene creates a new main menu scene. This is the first screen that the user // will see when launching the game. func NewMainMenuScene() *MainMenuScene { @@ -57,6 +63,7 @@ func (s *MainMenuScene) boot() { return } + s.setupViewports() s.createBackground() s.createButtons() s.createTrademarkScreen() @@ -65,6 +72,15 @@ func (s *MainMenuScene) boot() { s.booted = true } +func (s *MainMenuScene) setupViewports() { + s.Info("setting up viewports") + + imgPath := d2resource.GameSelectScreen + palPath := d2resource.PaletteSky + + s.sprites.mainBackground = s.Add.SegmentedSprite(0, 0, imgPath, palPath, 4, 3, 0) +} + func (s *MainMenuScene) createBackground() { s.Info("creating background") @@ -113,10 +129,16 @@ func (s *MainMenuScene) createTrademarkScreen() { alpha := s.AddAlpha(s.sprites.trademark) go func() { - a := 1.0 - for a > 0 { - a -= 0.125 - alpha.Alpha = a + alpha.Alpha = 1.0 + + for alpha.Alpha > 0 { + alpha.Alpha *= 0.725 + + if alpha.Alpha <= 1e-3 { + alpha.Alpha = 0 + return + } + time.Sleep(time.Second / 25) } @@ -187,7 +209,6 @@ func (s *MainMenuScene) initLogoSprites() { continue } - sprite.SetEffect(d2enum.DrawEffectModulate) sprite.PlayForward() } diff --git a/d2core/d2systems/scene_mouse_cursor.go b/d2core/d2systems/scene_mouse_cursor.go index 3ed20244..baff5a72 100644 --- a/d2core/d2systems/scene_mouse_cursor.go +++ b/d2core/d2systems/scene_mouse_cursor.go @@ -1,6 +1,9 @@ package d2systems import ( + "math" + "time" + "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -11,6 +14,11 @@ const ( sceneKeyMouseCursor = "Mouse Cursor" ) +const ( + fadeTimeout = time.Second * 4 + fadeTime = time.Second +) + // NewMouseCursorScene creates a new main menu scene. This is the first screen that the user // will see when launching the game. func NewMouseCursorScene() *MouseCursorScene { @@ -28,8 +36,9 @@ var _ d2interface.Scene = &MouseCursorScene{} // or start the map engine test. type MouseCursorScene struct { *BaseScene - booted bool - cursor akara.EID + booted bool + cursor akara.EID + lastTimeMoved time.Time } // Init the main menu scene @@ -70,6 +79,7 @@ func (s *MouseCursorScene) Update() { } s.updateCursorPosition() + s.handleCursorFade() s.BaseScene.Update() } @@ -80,6 +90,26 @@ func (s *MouseCursorScene) updateCursorPosition() { return } - cx, cy := s.systems.InputSystem.inputService.CursorPosition() + cx, cy := s.CursorPosition() + + if int(position.X) != cx || int(position.Y) != cy { + s.lastTimeMoved = time.Now() + } + position.X, position.Y = float64(cx), float64(cy) } + +func (s *MouseCursorScene) handleCursorFade() { + alpha, found := s.GetAlpha(s.cursor) + if !found { + return + } + + shouldFadeOut := time.Now().Sub(s.lastTimeMoved) > fadeTimeout + + if shouldFadeOut { + alpha.Alpha = math.Max(alpha.Alpha*0.825, 0) + } else { + alpha.Alpha = math.Min(alpha.Alpha+0.125, 1) + } +} diff --git a/d2core/d2systems/scene_object_factory.go b/d2core/d2systems/scene_object_factory.go index 77b2d0b8..92c1d18e 100644 --- a/d2core/d2systems/scene_object_factory.go +++ b/d2core/d2systems/scene_object_factory.go @@ -1,6 +1,7 @@ package d2systems import ( + "image/color" "path/filepath" "github.com/gravestench/akara" @@ -14,7 +15,7 @@ type sceneObjectFactory struct { *d2util.Logger } -func (s *sceneObjectFactory) addBasicComponenets(id akara.EID) { +func (s *sceneObjectFactory) addBasicComponents(id akara.EID) { node := s.AddSceneGraphNode(id) node.SetParent(s.Graph) @@ -25,10 +26,10 @@ func (s *sceneObjectFactory) addBasicComponenets(id akara.EID) { func (s *sceneObjectFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID { s.Infof("creating sprite: %s, %s", filepath.Base(imgPath), palPath) - eid := s.systems.SpriteFactory.Sprite(x, y, imgPath, palPath) + eid := s.baseSystems.SpriteFactory.Sprite(x, y, imgPath, palPath) s.GameObjects = append(s.GameObjects, eid) - s.addBasicComponenets(eid) + s.addBasicComponents(eid) return eid } @@ -36,10 +37,50 @@ func (s *sceneObjectFactory) Sprite(x, y float64, imgPath, palPath string) akara func (s *sceneObjectFactory) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID { s.Infof("creating segmented sprite: %s, %s", filepath.Base(imgPath), palPath) - eid := s.systems.SpriteFactory.SegmentedSprite(x, y, imgPath, palPath, xseg, yseg, frame) + eid := s.baseSystems.SpriteFactory.SegmentedSprite(x, y, imgPath, palPath, xseg, yseg, frame) s.GameObjects = append(s.GameObjects, eid) - s.addBasicComponenets(eid) + s.addBasicComponents(eid) + + return eid +} + +func (s *sceneObjectFactory) Viewport(priority, width, height int) akara.EID { + s.Infof("creating viewport #%d", priority) + + eid := s.NewEntity() + s.AddViewport(eid) + s.AddPriority(eid).Priority = priority + + if priority == mainViewport { + s.AddMainViewport(eid) + } + + camera := s.AddCamera(eid) + camera.Size.X = float64(width) + camera.Size.Y = float64(height) + + sfc := s.baseSystems.RenderSystem.renderer.NewSurface(width, height) + + sfc.Clear(color.Transparent) + + s.AddTexture(eid).Texture = sfc + + s.Viewports = append(s.Viewports, eid) + + s.addBasicComponents(eid) + + return eid +} + +func (s *sceneObjectFactory) Rectangle(x, y, width, height int, color color.Color) akara.EID { + s.Info("creating rectangle") + + eid := s.baseSystems.ShapeSystem.Rectangle(x, y, width, height, color) + + s.addBasicComponents(eid) + + s.GameObjects = append(s.GameObjects, eid) return eid } diff --git a/d2core/d2systems/scene_shape_system.go b/d2core/d2systems/scene_shape_system.go new file mode 100644 index 00000000..ed244d82 --- /dev/null +++ b/d2core/d2systems/scene_shape_system.go @@ -0,0 +1,151 @@ +package d2systems + +import ( + "image/color" + + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" +) + +// NewShapeSystem creates a new sprite factory which is intended +// to be embedded in the game object factory system. +func NewShapeSystem(b akara.BaseSystem, l *d2util.Logger) *ShapeSystem { + sys := &ShapeSystem{ + Logger: l, + } + + sys.BaseSystem = b + + sys.World.AddSystem(sys) + + return sys +} + +// ShapeSystem is responsible for queueing sprites to be loaded (as spriteations), +// as well as binding the spriteation to a renderer if one is present (which generates the sprite surfaces). +type ShapeSystem struct { + akara.BaseSubscriberSystem + *d2util.Logger + RenderSystem *RenderSystem + d2components.PositionFactory + d2components.ColorFactory + d2components.RectangleFactory + d2components.TextureFactory + d2components.SizeFactory + d2components.OriginFactory + loadQueue spriteLoadQueue + shapesToRender *akara.Subscription + shapesToUpdate *akara.Subscription +} + +// Init the sprite factory, injecting the necessary components +func (t *ShapeSystem) Init(world *akara.World) { + t.World = world + + t.Info("initializing sprite factory ...") + + t.setupFactories() + t.setupSubscriptions() + + t.loadQueue = make(spriteLoadQueue) +} + +func (t *ShapeSystem) setupFactories() { + t.InjectComponent(&d2components.Color{}, &t.ColorFactory.Color) + t.InjectComponent(&d2components.Position{}, &t.PositionFactory.Position) + t.InjectComponent(&d2components.Texture{}, &t.TextureFactory.Texture) + t.InjectComponent(&d2components.Origin{}, &t.OriginFactory.Origin) + t.InjectComponent(&d2components.Size{}, &t.SizeFactory.Size) + t.InjectComponent(&d2components.Rectangle{}, &t.RectangleFactory.Rectangle) +} + +func (t *ShapeSystem) setupSubscriptions() { + shapesToRender := t.NewComponentFilter(). + RequireOne(&d2components.Rectangle{}). + Require(&d2components.Texture{}). + Build() + + shapesToUpdate := t.NewComponentFilter(). + RequireOne(&d2components.Rectangle{}). + Require(&d2components.Position{}, &d2components.Size{}). + Build() + + t.shapesToRender = t.AddSubscription(shapesToRender) + t.shapesToUpdate = t.AddSubscription(shapesToUpdate) +} + +// Update processes the load queue which attempting to create spriteations, as well as +// binding existing spriteations to a renderer if one is present. +func (t *ShapeSystem) Update() { + for _, id := range t.shapesToUpdate.GetEntities() { + t.updateShape(id) + } + + for _, id := range t.shapesToRender.GetEntities() { + t.renderShape(id) + } +} + +// Sprite queues a sprite spriteation to be loaded +func (t *ShapeSystem) Rectangle(x, y, width, height int, color color.Color) akara.EID { + t.Info("creating rectangle") + + eid := t.NewEntity() + r := t.AddRectangle(eid) + + r.X, r.Y = float64(x), float64(y) + r.Width, r.Height = float64(width), float64(height) + + c := t.AddColor(eid) + c.Color = color + + texture := t.AddTexture(eid) + texture.Texture = t.RenderSystem.renderer.NewSurface(width, height) + texture.Texture.Clear(c.Color) + + return eid +} + +func (t *ShapeSystem) updateShape(eid akara.EID) { + position, found := t.GetPosition(eid) + if !found { + return + } + + size, found := t.GetSize(eid) + if !found { + return + } + + texture, found := t.GetTexture(eid) + if !found || texture.Texture == nil { + return + } + + rectangle, rectangleFound := t.GetRectangle(eid) + if rectangleFound { + position.X, position.Y = rectangle.X, rectangle.Y + size.X, size.Y = rectangle.Width, rectangle.Height + + tw, th := texture.Texture.GetSize() + if tw != int(size.X) || th != int(size.Y) { + texture.Texture.Renderer().NewSurface(int(size.X), int(size.Y)) + } + } +} + +func (t *ShapeSystem) renderShape(eid akara.EID) { + texture, found := t.GetTexture(eid) + if !found || texture.Texture == nil { + return + } + + col, found := t.GetColor(eid) + if !found { + return + } + + texture.Texture.Clear(col.Color) +} diff --git a/d2core/d2systems/sprite_factory.go b/d2core/d2systems/scene_sprite_system.go similarity index 86% rename from d2core/d2systems/sprite_factory.go rename to d2core/d2systems/scene_sprite_system.go index 7b38792d..a341ad2d 100644 --- a/d2core/d2systems/sprite_factory.go +++ b/d2core/d2systems/scene_sprite_system.go @@ -66,25 +66,15 @@ func (t *SpriteFactory) Init(world *akara.World) { } func (t *SpriteFactory) setupFactories() { - filePathID := t.RegisterComponent(&d2components.FilePath{}) - positionID := t.RegisterComponent(&d2components.Position{}) - dc6ID := t.RegisterComponent(&d2components.Dc6{}) - dccID := t.RegisterComponent(&d2components.Dcc{}) - paletteID := t.RegisterComponent(&d2components.Palette{}) - spriteID := t.RegisterComponent(&d2components.Sprite{}) - textureID := t.RegisterComponent(&d2components.Texture{}) - originID := t.RegisterComponent(&d2components.Origin{}) - segmentedSpriteID := t.RegisterComponent(&d2components.SegmentedSprite{}) - - t.FilePath = t.GetComponentFactory(filePathID) - t.Position = t.GetComponentFactory(positionID) - t.Dc6 = t.GetComponentFactory(dc6ID) - t.Dcc = t.GetComponentFactory(dccID) - t.Palette = t.GetComponentFactory(paletteID) - t.Texture = t.GetComponentFactory(textureID) - t.Origin = t.GetComponentFactory(originID) - t.SpriteFactory.Sprite = t.GetComponentFactory(spriteID) - t.SegmentedSpriteFactory.SegmentedSprite = t.GetComponentFactory(segmentedSpriteID) + t.InjectComponent(&d2components.FilePath{}, &t.FilePath) + t.InjectComponent(&d2components.Position{}, &t.Position) + t.InjectComponent(&d2components.Dc6{}, &t.Dc6) + t.InjectComponent(&d2components.Dcc{}, &t.Dcc) + t.InjectComponent(&d2components.Palette{}, &t.Palette) + t.InjectComponent(&d2components.Texture{}, &t.Texture) + t.InjectComponent(&d2components.Origin{}, &t.Origin) + t.InjectComponent(&d2components.Sprite{}, &t.SpriteFactory.Sprite) + t.InjectComponent(&d2components.SegmentedSprite{}, &t.SegmentedSpriteFactory.SegmentedSprite) } func (t *SpriteFactory) setupSubscriptions() { diff --git a/d2core/d2systems/scene_terminal.go b/d2core/d2systems/scene_terminal.go new file mode 100644 index 00000000..87870ae7 --- /dev/null +++ b/d2core/d2systems/scene_terminal.go @@ -0,0 +1,107 @@ +package d2systems + +import ( + "image/color" + + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" +) + +const ( + sceneKeyTerminal = "Terminal" +) + +// static check that TerminalScene implements the scene interface +var _ d2interface.Scene = &TerminalScene{} + +// NewTerminalScene creates a new main menu scene. This is the first screen that the user +// will see when launching the game. +func NewTerminalScene() *TerminalScene { + scene := &TerminalScene{ + BaseScene: NewBaseScene(sceneKeyTerminal), + } + + return scene +} + +// TerminalScene represents the game's loading screen, where loading progress is displayed +type TerminalScene struct { + *BaseScene + booted bool +} + +// Init the loading scene +func (s *TerminalScene) Init(world *akara.World) { + s.World = world + + s.Info("initializing ...") + + s.backgroundColor = color.Black + + s.setupFactories() + s.setupSubscriptions() +} + +func (s *TerminalScene) setupFactories() { + texture := s.RegisterComponent(&d2components.Texture{}) + s.Texture = s.GetComponentFactory(texture) +} + +func (s *TerminalScene) setupSubscriptions() { + s.Info("setting up component subscriptions") + + //stage1 := s.NewComponentFilter(). + // Require( + // &d2components.FilePath{}, + // ). + // Forbid( // but we forbid files that are already loaded + // &d2components.FileType{}, + // &d2components.FileHandle{}, + // &d2components.FileSource{}, + // &d2components.GameConfig{}, + // &d2components.StringTable{}, + // &d2components.DataDictionary{}, + // &d2components.Palette{}, + // &d2components.PaletteTransform{}, + // &d2components.Cof{}, + // &d2components.Dc6{}, + // &d2components.Dcc{}, + // &d2components.Ds1{}, + // &d2components.Dt1{}, + // &d2components.Wav{}, + // &d2components.AnimationData{}, + // ). + // Build() + + //s.loadStages.stage1 = s.World.AddSubscription(stage1) +} + +func (s *TerminalScene) boot() { + if !s.BaseScene.booted { + s.BaseScene.boot() + return + } + + //s.createTerminalScene() + + s.booted = true +} + +// Update the loading scene +func (s *TerminalScene) Update() { + for _, id := range s.Viewports { + s.AddPriority(id).Priority = scenePriorityLoading + } + + if s.Paused() { + return + } + + if !s.booted { + s.boot() + } + + s.BaseScene.Update() +} diff --git a/go.mod b/go.mod index 78e54f9d..d68a189c 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,15 @@ go 1.14 require ( github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 // indirect + github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 // indirect github.com/go-restruct/restruct v1.2.0-alpha github.com/google/uuid v1.1.2 - github.com/gravestench/akara v0.0.0-20201128054238-892de9d70d6b - github.com/hajimehoshi/ebiten/v2 v2.0.0 - github.com/pkg/errors v0.9.1 // indirect + github.com/gravestench/akara v0.0.0-20201202061557-d347a52d5532 + github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d + github.com/hajimehoshi/ebiten/v2 v2.0.1 github.com/pkg/profile v1.5.0 github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac - github.com/stretchr/testify v1.4.0 - golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 // indirect golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 968b9147..959e60f0 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928= +github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -21,14 +23,25 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gravestench/akara v0.0.0-20201128054238-892de9d70d6b h1:Ngfdn7O3wXQBzbOLsL6vQ9G4F7utUiKjQqKnwHbY5uI= github.com/gravestench/akara v0.0.0-20201128054238-892de9d70d6b/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= +github.com/gravestench/akara v0.0.0-20201201101207-7cceed226bf1 h1:lcvisQZBoEH41Ed9dLrBApmF4+CKwXeL8T3+SdGfpvc= +github.com/gravestench/akara v0.0.0-20201201101207-7cceed226bf1/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= +github.com/gravestench/akara v0.0.0-20201202061557-d347a52d5532 h1:hGNjc5HCLUTk6kJpM3x6hfDcYxZ0I6LYDrKFR/4vZOQ= +github.com/gravestench/akara v0.0.0-20201202061557-d347a52d5532/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= +github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d h1:CP+/y9SAdv9LifYvicxYdQNmzugykEahAiUhYolMROM= +github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d/go.mod h1:yi5GHMLLWtHhs9tz3q1csUlgGKz5MhZoJcxV8NFBtkk= github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= +github.com/hajimehoshi/ebiten v1.12.4 h1:ie1lGp9mwPeIopCMMZ6wY5O45mfXmZOuixGHxsTKSNc= github.com/hajimehoshi/ebiten/v2 v2.0.0 h1:G8mhkKFtnDPPZ/ChaGWx4Bm0NusYEcafGCJ8QLxEaYs= github.com/hajimehoshi/ebiten/v2 v2.0.0/go.mod h1:hpZZQ/kk8DZqft7QsQ5hZLRQXHSZPdKnaa0tcJ3CZFE= +github.com/hajimehoshi/ebiten/v2 v2.0.1 h1:94ucoKKoqiJOZxDod8gdMrroCDy0CO6Ct+Nc9kjsW98= +github.com/hajimehoshi/ebiten/v2 v2.0.1/go.mod h1:AbHP/SS226aFTex/izULVwW0D2AuGyqC4AVwilmRjOg= github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto v0.6.6 h1:HYSZ8cYZqOL4iHugvbcfhNN2smiSOsBMaoSBi4nnWcw= github.com/hajimehoshi/oto v0.6.6/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= +github.com/hajimehoshi/oto v0.6.8 h1:yRb3EJQ4lAkBgZYheqmdH6Lr77RV9nSWFsK/jwWdTNY= +github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= @@ -92,6 +105,8 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5v golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=