Files
ordoor/internal/flow/flow.go
Nick Thomas 5f8606377a Integrate view-map into ordoor
Now we can view scenario maps from the main game interface. We can't
cancel out of a scenario yet, though.
2020-04-11 00:13:28 +01:00

299 lines
6.6 KiB
Go

package flow
import (
"errors"
"fmt"
"log"
"github.com/hajimehoshi/ebiten"
"code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config"
"code.ur.gs/lupine/ordoor/internal/data"
"code.ur.gs/lupine/ordoor/internal/scenario"
"code.ur.gs/lupine/ordoor/internal/ship"
"code.ur.gs/lupine/ordoor/internal/ui"
)
// type Flow is responsible for wiring up UI elements to each other and ensuring
// they behave as expected. This includes forward / back buttons to switch
// between screens, loading and saving options, launching a scenario, etc
type Flow struct {
assets *assetstore.AssetStore
config *config.Config
current *ui.Driver
drivers map[driverName]*ui.Driver
generic *data.Generic
// Some screens can be returned to from more than one place. Where this is
// the case, instead of hardcoding it, we'll store an entry in here so we
// know where we're going back to
//
// FIXME: this really suggests wiring everything up at the start is wrong.
returns map[driverName]driverName
// If we're currently playing a scenario, it it placed here
scenario *scenario.Scenario
ship *ship.Ship
exit error
}
type driverName string
const (
// Names of all the drivers
main driverName = "Main"
levelPly driverName = "LevelPly"
singles driverName = "Singles"
randomMap driverName = "RandomMap"
newGame driverName = "NewGame"
loadGame driverName = "LoadGame"
options driverName = "Options"
kbd driverName = "Keyboard"
bridge driverName = "Bridge"
briefing driverName = "Briefing"
choices driverName = "Choices"
saveGame driverName = "SaveGame"
credits driverName = "Credits"
arrange driverName = "Arrange"
configureUltEquip driverName = "Configure_UltEquip"
configureVehiclesUltra driverName = "Configure_Vehicles_Ultra"
)
var (
ErrExit = errors.New("exiting gracefully")
driverNames = []driverName{
main, levelPly, singles, randomMap, newGame, loadGame, options, kbd,
bridge, briefing, choices, saveGame, credits, arrange,
configureUltEquip, configureVehiclesUltra,
}
// Constants used for sliders
h3Slider = map[int]int{1: 8, 2: 56, 3: 110, 4: 120}
v10Slider = map[int]int{
0: 0,
10: 9, 20: 18, 30: 27, 40: 36, 50: 45,
60: 54, 70: 63, 80: 72, 90: 81, 100: 90,
}
h9Slider = map[int]int{
0: 0,
10: 10, 20: 20, 30: 30, 40: 40,
50: 50, 60: 60, 70: 70, 80: 80,
}
)
func New(assets *assetstore.AssetStore, config *config.Config, ship *ship.Ship) (*Flow, error) {
generic, err := assets.Generic()
if err != nil {
return nil, fmt.Errorf("Failed to read generic data: %v", err)
}
out := &Flow{
assets: assets,
config: config,
generic: generic,
drivers: make(map[driverName]*ui.Driver, len(driverNames)),
returns: make(map[driverName]driverName),
ship: ship,
}
// Load all the drivers upfront
for _, name := range driverNames {
driver, err := buildDriver(assets, name)
if err != nil {
return nil, err
}
out.drivers[name] = driver
}
// Initial load of the config into the options UI
if err := out.configIntoOptions(); err != nil {
return nil, err
}
out.linkDrivers()
out.setDriverNow(main)
return out, nil
}
func buildDriver(assets *assetstore.AssetStore, name driverName) (*ui.Driver, error) {
menu, err := assets.Menu(string(name))
if err != nil {
return nil, err
}
driver, err := ui.NewDriver(assets, menu)
if err != nil {
return nil, err
}
return driver, nil
}
func (f *Flow) Update(screenX, screenY int) error {
if f.exit != nil {
return f.exit
}
if f.scenario != nil {
if err := f.scenario.Update(screenX, screenY); err != nil {
return err
}
}
if f.current != nil {
if err := f.current.Update(screenX, screenY); err != nil {
return err
}
}
return nil
}
func (f *Flow) Draw(screen *ebiten.Image) error {
if f.exit != nil {
return f.exit
}
if f.scenario != nil {
if err := f.scenario.Draw(screen); err != nil {
return err
}
}
if f.current != nil {
if err := f.current.Draw(screen); err != nil {
return err
}
}
return nil
}
func (f *Flow) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
if f.current != nil {
return f.current.Cursor()
}
// FIXME: we should get a cursor from current all the time.
return nil, nil, nil
}
func (f *Flow) linkDrivers() {
// linkMain
f.onClick(main, "2.1", f.setDriver(newGame)) // New game
f.onClick(main, "2.2", f.setDriver(loadGame)) // Load game
f.setFreeze(main, "2.3", true) // Multiplayer - disable for now
f.onClick(main, "2.4", f.setReturningDriver(main, options)) // Options
f.onClick(main, "2.5", f.setExit) // Quit
// Now link immediate children. They will link their children, and so on
f.linkNewGame()
f.linkLoadGame()
// TODO: link multiplayer
f.linkOptions()
}
func maybeErr(driver driverName, err error) error {
if err != nil {
return fmt.Errorf("%v: %v", driver, err)
}
return nil
}
func (f *Flow) configureSlider(driver driverName, id string, steps map[int]int) {
if f.exit != nil {
return
}
f.exit = f.drivers[driver].ConfigureSlider(id, steps)
}
func (f *Flow) onClick(driver driverName, id string, fn func()) {
if f.exit != nil {
return
}
f.exit = maybeErr(driver, f.drivers[driver].OnClick(id, fn))
}
func (f *Flow) setFreeze(driver driverName, id string, value bool) {
if f.exit != nil {
return
}
f.exit = maybeErr(driver, f.drivers[driver].SetFreeze(id, value))
}
func (f *Flow) setValueBool(driver driverName, id string, value bool) {
if f.exit != nil {
return
}
f.exit = f.drivers[driver].SetValueBool(id, value)
}
func (f *Flow) setDriver(name driverName) func() {
return func() {
f.setDriverNow(name)
}
}
func (f *Flow) setDriverNow(name driverName) {
f.current = f.drivers[name]
}
// from is the parent menu, to is the child
func (f *Flow) setReturningDriver(from, to driverName) func() {
return func() {
f.returns[to] = from
f.setDriverNow(to)
}
}
func (f *Flow) playNextScenario() func() {
return func() {
log.Printf("Loading scenario: %v", f.ship.NextScenario)
// TODO: we *could* load scenario assets in a separate assetstore to
// make it easier to chuck them away at the end?
scenario, err := scenario.NewScenario(f.assets, f.ship.NextScenario)
if err != nil {
f.exit = err
return
}
f.current = nil // TODO: show the UI for a scenario
f.scenario = scenario
}
}
// from is the child menu, to is the parent
func (f *Flow) returnToLastDriverNow(from driverName) error {
to, ok := f.returns[from]
if !ok {
return fmt.Errorf("Couldn't work out where to return to from %v", from)
}
delete(f.returns, from)
f.setDriverNow(to)
return nil
}
func (f *Flow) setExit() {
f.exit = ErrExit
}