Files
ordoor/internal/ordoor/ordoor.go

174 lines
3.7 KiB
Go

// package ordoor implements the full WH40K.EXE functionality, and is used from
// cmd/ordoor/main.go
//
// Entrypoint is Run()
package ordoor
import (
"fmt"
"log"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/audio"
"code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config"
"code.ur.gs/lupine/ordoor/internal/flow"
"code.ur.gs/lupine/ordoor/internal/ship"
"code.ur.gs/lupine/ordoor/internal/ui"
)
type gameState int
type Ordoor struct {
assets *assetstore.AssetStore
config *config.Config
music *audio.Player
win *ui.Window
// Relevant to interface state
flow *flow.Flow
// Relevant to campaign state
ship *ship.Ship
}
func Run(configFile string, overrideX, overrideY int) error {
cfg, err := config.Load(configFile, "ordoor")
if err != nil {
return fmt.Errorf("Couldn't load config file: %v", err)
}
assets, err := assetstore.New(cfg.DefaultEngine())
if err != nil {
return fmt.Errorf("Failed to initialize asset store: %v", err)
}
defaults, err := assets.DefaultOptions()
if err != nil {
return fmt.Errorf("Failed to read option defaults: %v", err)
}
cfg.Defaults = defaults
if cfg.HasUnsetOptions() {
if err := cfg.ResetDefaults(); err != nil {
return fmt.Errorf("Failed to set options on first-start: %v", err)
}
}
if _, err := audio.NewContext(48000); err != nil {
return fmt.Errorf("Failed to set up audio context: %v", err)
}
ordoor := &Ordoor{
assets: assets,
config: cfg,
ship: ship.New(),
}
x, y := cfg.Options.XRes, cfg.Options.YRes
if overrideX > 0 {
x = overrideX
}
if overrideY > 0 {
y = overrideY
}
win, err := ui.NewWindow(ordoor, "Ordoor", x, y)
if err != nil {
return fmt.Errorf("Failed to create window: %v", err)
}
ordoor.win = win
if err := ordoor.setupFlow(); err != nil {
return fmt.Errorf("failed to setup UI flow: %v", err)
}
if err := ordoor.Run(); err != nil {
return fmt.Errorf("Run finished with error: %v", err)
}
return nil
}
func (o *Ordoor) Run() error {
// FIXME: we're missing a screen about SSI here
if o.config.Options.PlayMovies {
o.PlaySkippableVideo("LOGOS")
o.PlaySkippableVideo("movie1")
}
err := o.win.Run()
if err == flow.ErrExit {
log.Printf("Exit requested")
return nil
}
return err
}
// Only one music track can play at a time. This is handled at the toplevel.
// FIXME: should take references from Sounds.dat
// FIXME: music probably properly belongs to flow. This package can just do
// initialization and wire the flow to the ship?
func (o *Ordoor) PlayMusic(name string) error {
if o.music != nil {
if err := o.music.Close(); err != nil {
return fmt.Errorf("Failed to close old music: %v", err)
}
}
sound, err := o.assets.Sound(name)
if err != nil {
return fmt.Errorf("Failed to find sound %v: %v", name, err)
}
player, err := sound.InfinitePlayer()
if err != nil {
return fmt.Errorf("Failed to generate music player for %v: %v", name, err)
}
o.music = player
if o.config.Options.PlayMusic {
player.Play()
}
return nil
}
func (o *Ordoor) setupFlow() error {
o.PlayMusic("music_interface")
flow, err := flow.New(o.assets, o.config, o.ship)
if err != nil {
return err
}
o.flow = flow
return nil
}
func (o *Ordoor) Update(screenX, screenY int) error {
// Ensure music is doing the right thing
if o.music != nil && o.music.IsPlaying() != o.config.Options.PlayMusic {
if o.config.Options.PlayMusic {
o.music.Rewind()
o.music.Play()
} else {
o.music.Pause()
}
}
return o.flow.Update(screenX, screenY)
}
func (o *Ordoor) Draw(screen *ebiten.Image) error {
return o.flow.Draw(screen)
}
func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
return o.flow.Cursor()
}