2020-03-22 17:19:26 +00:00
|
|
|
// package ordoor implements the full WH40K.EXE functionality, and is used from
|
|
|
|
// cmd/ordoor/main.go
|
2018-10-13 03:24:10 +01:00
|
|
|
//
|
|
|
|
// Entrypoint is Run()
|
2020-03-22 17:19:26 +00:00
|
|
|
package ordoor
|
2018-10-13 03:24:10 +01:00
|
|
|
|
|
|
|
import (
|
2020-03-22 19:12:44 +00:00
|
|
|
"errors"
|
2018-10-13 03:24:10 +01:00
|
|
|
"fmt"
|
2020-03-22 19:12:44 +00:00
|
|
|
"log"
|
2018-10-13 03:24:10 +01:00
|
|
|
|
2020-03-22 19:12:44 +00:00
|
|
|
"github.com/hajimehoshi/ebiten"
|
|
|
|
"github.com/hajimehoshi/ebiten/audio"
|
|
|
|
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
2019-12-31 01:55:58 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/config"
|
2020-03-25 02:12:17 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/ordoor/flow"
|
2020-03-22 19:12:44 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/ui"
|
|
|
|
)
|
|
|
|
|
|
|
|
type gameState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
StateInterface gameState = 1
|
|
|
|
StateExit gameState = 666
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errExit = errors.New("User-requested exit action")
|
2018-10-13 03:24:10 +01:00
|
|
|
)
|
|
|
|
|
2020-03-22 17:19:26 +00:00
|
|
|
type Ordoor struct {
|
2020-03-22 19:12:44 +00:00
|
|
|
assets *assetstore.AssetStore
|
|
|
|
config *config.Config
|
|
|
|
music *audio.Player
|
|
|
|
win *ui.Window
|
|
|
|
|
|
|
|
state gameState
|
|
|
|
nextState gameState
|
|
|
|
|
|
|
|
// Relevant to interface state
|
2020-03-25 02:12:17 +00:00
|
|
|
flow *flow.Flow
|
2018-10-13 03:24:10 +01:00
|
|
|
}
|
|
|
|
|
2020-03-23 00:33:29 +00:00
|
|
|
func Run(configFile string, overrideX, overrideY int) error {
|
2018-10-13 03:24:10 +01:00
|
|
|
cfg, err := config.Load(configFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Couldn't load config file: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-03-22 19:12:44 +00:00
|
|
|
assets, err := assetstore.New(cfg.Ordoor.DataDir)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to initialize asset store: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := audio.NewContext(48000); err != nil {
|
|
|
|
return fmt.Errorf("Failed to set up audio context: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-03-22 17:19:26 +00:00
|
|
|
ordoor := &Ordoor{
|
2020-03-22 19:12:44 +00:00
|
|
|
assets: assets,
|
|
|
|
config: cfg,
|
2020-03-25 02:12:17 +00:00
|
|
|
state: StateInterface,
|
2020-03-22 19:12:44 +00:00
|
|
|
nextState: StateInterface,
|
2018-10-13 03:24:10 +01:00
|
|
|
}
|
|
|
|
|
2020-03-23 00:33:29 +00:00
|
|
|
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)
|
2020-03-22 19:12:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to create window: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ordoor.win = win
|
2018-10-13 03:24:10 +01:00
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
if err := ordoor.setupFlow(); err != nil {
|
|
|
|
return fmt.Errorf("failed to setup UI flow: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-03-22 19:12:44 +00:00
|
|
|
if err := ordoor.Run(); err != nil {
|
|
|
|
return fmt.Errorf("Run returned %v", err)
|
|
|
|
}
|
2020-03-22 02:58:52 +00:00
|
|
|
|
2018-10-13 03:24:10 +01:00
|
|
|
return nil
|
|
|
|
}
|
2020-03-22 19:12:44 +00:00
|
|
|
|
|
|
|
func (o *Ordoor) Run() error {
|
2020-03-26 23:35:34 +00:00
|
|
|
// FIXME: we're missing a screen about SSI here
|
2020-03-22 22:12:59 +00:00
|
|
|
if o.config.Options.PlayMovies {
|
|
|
|
o.PlayUnskippableVideo("LOGOS")
|
|
|
|
o.PlaySkippableVideo("movie1")
|
|
|
|
}
|
2020-03-22 19:12:44 +00:00
|
|
|
|
|
|
|
err := o.win.Run()
|
2020-03-25 02:12:17 +00:00
|
|
|
if err == flow.ErrExit {
|
2020-03-22 22:12:59 +00:00
|
|
|
log.Printf("Exit requested")
|
2020-03-22 19:12:44 +00:00
|
|
|
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
|
|
|
|
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
|
2020-03-23 00:33:29 +00:00
|
|
|
|
|
|
|
if o.config.Options.PlayMusic {
|
|
|
|
player.Play()
|
|
|
|
}
|
2020-03-22 19:12:44 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
func (o *Ordoor) setupFlow() error {
|
2020-03-22 19:12:44 +00:00
|
|
|
o.PlayMusic("music_interface")
|
2020-03-25 02:12:17 +00:00
|
|
|
|
|
|
|
flow, err := flow.New(o.assets, o.config)
|
2020-03-22 19:12:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
o.flow = flow
|
2020-03-22 19:12:44 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Ordoor) Update(screenX, screenY int) error {
|
|
|
|
// Perform state transitions
|
|
|
|
if o.state != o.nextState {
|
|
|
|
log.Printf("State transition: %v -> %v", o.state, o.nextState)
|
|
|
|
switch o.nextState {
|
|
|
|
case StateExit:
|
|
|
|
{
|
|
|
|
return errExit
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown state transition: %v -> %v", o.state, o.nextState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// State transition is finished, hooray
|
|
|
|
o.state = o.nextState
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-22 19:12:44 +00:00
|
|
|
switch o.state {
|
|
|
|
case StateInterface:
|
2020-03-25 02:12:17 +00:00
|
|
|
return o.flow.Update(screenX, screenY)
|
2020-03-22 19:12:44 +00:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown state: %v", o.state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Ordoor) Draw(screen *ebiten.Image) error {
|
|
|
|
switch o.state {
|
|
|
|
case StateInterface:
|
2020-03-25 02:12:17 +00:00
|
|
|
return o.flow.Draw(screen)
|
2020-03-22 19:12:44 +00:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown state: %v", o.state)
|
|
|
|
}
|
|
|
|
}
|