Move internal/ordoor/flow to internal/flow
This commit is contained in:
47
internal/flow/bridge.go
Normal file
47
internal/flow/bridge.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package flow
|
||||
|
||||
func (f *Flow) linkBridge() {
|
||||
// FIXME: sometimes these doors are frozen, depending on game state
|
||||
|
||||
f.onClick(bridge, "2.1", f.setDriver(briefing)) // TODO: Mission briefing clickable
|
||||
f.onClick(bridge, "2.2", f.setDriver(choices)) // Options door hotspot
|
||||
f.setFreeze(bridge, "2.4", false) // FIXME: Enter combat door hotspot (!!!)
|
||||
f.setFreeze(bridge, "2.6", false) // FIXME: Vehicle configure door hotspot
|
||||
f.onClick(bridge, "2.8", f.setDriver(arrange)) // Squads configure door hotspot
|
||||
|
||||
// link children
|
||||
f.linkBriefing()
|
||||
f.linkChoices()
|
||||
f.linkArrange()
|
||||
}
|
||||
|
||||
func (f *Flow) linkBriefing() {
|
||||
f.onClick(briefing, "3.1", f.setDriver(bridge))
|
||||
}
|
||||
|
||||
func (f *Flow) linkChoices() {
|
||||
f.onClick(choices, "2.1", f.setDriver(loadGame)) // Load another game button
|
||||
f.onClick(choices, "2.2", f.setDriver(saveGame)) // Save this game button
|
||||
f.onClick(choices, "2.3", f.setReturningDriver(choices, options)) // More options button
|
||||
|
||||
// FIXME: wipe out game state when this goes through
|
||||
f.onClick(choices, "2.4", f.setDriver(main)) // Restart button
|
||||
|
||||
f.onClick(choices, "2.5", f.setDriver(credits)) // Credits button
|
||||
f.onClick(choices, "2.6", f.setExit) // Quit button
|
||||
f.onClick(choices, "2.7", f.setDriver(bridge)) // Back button
|
||||
}
|
||||
|
||||
func (f *Flow) linkArrange() {
|
||||
// FIXME: we should be operating on game data in here
|
||||
f.onClick(arrange, "8.1", f.setDriver(bridge)) // Return to bridge ("cathedral")
|
||||
f.onClick(arrange, "8.3", f.setDriver(configureUltEquip)) // Configure squads
|
||||
|
||||
f.linkConfigureUltEquip()
|
||||
}
|
||||
|
||||
func (f *Flow) linkConfigureUltEquip() {
|
||||
// FIXME: we should be modifying loadouts of selected squad members here
|
||||
|
||||
f.onClick(configureUltEquip, "8.1", f.setDriver(bridge)) // Return to bridge
|
||||
}
|
233
internal/flow/flow.go
Normal file
233
internal/flow/flow.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||
"code.ur.gs/lupine/ordoor/internal/config"
|
||||
"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
|
||||
|
||||
// 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
|
||||
|
||||
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) (*Flow, error) {
|
||||
out := &Flow{
|
||||
assets: assets,
|
||||
config: config,
|
||||
drivers: make(map[driverName]*ui.Driver, len(driverNames)),
|
||||
returns: make(map[driverName]driverName),
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return f.current.Update(screenX, screenY)
|
||||
}
|
||||
|
||||
func (f *Flow) Draw(screen *ebiten.Image) error {
|
||||
if f.exit != nil {
|
||||
return f.exit
|
||||
}
|
||||
|
||||
return f.current.Draw(screen)
|
||||
}
|
||||
|
||||
func (f *Flow) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
||||
return f.current.Cursor()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
6
internal/flow/load_game.go
Normal file
6
internal/flow/load_game.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package flow
|
||||
|
||||
func (f *Flow) linkLoadGame() {
|
||||
// Load game
|
||||
f.onClick(loadGame, "3.3", f.setDriver(main)) // Cancel button
|
||||
}
|
44
internal/flow/new_game.go
Normal file
44
internal/flow/new_game.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package flow
|
||||
|
||||
func (f *Flow) linkNewGame() {
|
||||
// New game
|
||||
f.onClick(newGame, "2.1", f.setDriver(levelPly)) // New campaign button
|
||||
f.onClick(newGame, "2.2", f.setDriver(singles)) // Single scenario button
|
||||
f.onClick(newGame, "2.3", f.setDriver(randomMap)) // Random scenario button
|
||||
f.onClick(newGame, "2.4", f.setDriver(main)) // Back button
|
||||
|
||||
f.linkLevelPly()
|
||||
f.linkSingles()
|
||||
f.linkRandomMap()
|
||||
}
|
||||
|
||||
func (f *Flow) linkLevelPly() {
|
||||
// We want the default difficulty level to be Veteran, not Hero.
|
||||
// FIXME: Make the radio button respect changes via setValue
|
||||
resetLevel := func() {
|
||||
f.setValueBool(levelPly, "2.1", false)
|
||||
f.setValueBool(levelPly, "2.2", true)
|
||||
}
|
||||
resetLevel()
|
||||
|
||||
f.onClick(levelPly, "2.5", func() { // Back button
|
||||
resetLevel()
|
||||
f.setDriverNow(newGame)
|
||||
})
|
||||
|
||||
// FIXME: we should select a savegame if Mighty Hero is selected here
|
||||
// FIXME: we should show a movie here. Need an internal SMK player first
|
||||
// FIXME: we should set up new game state here!
|
||||
f.onClick(levelPly, "2.7", f.setDriver(bridge)) // Select button
|
||||
|
||||
// Link children
|
||||
f.linkBridge()
|
||||
}
|
||||
|
||||
func (f *Flow) linkSingles() {
|
||||
f.onClick(singles, "4.11", f.setDriver(newGame)) // Back button
|
||||
}
|
||||
|
||||
func (f *Flow) linkRandomMap() {
|
||||
f.onClick(randomMap, "2.19", f.setDriver(newGame)) // Back button
|
||||
}
|
109
internal/flow/options.go
Normal file
109
internal/flow/options.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func (f *Flow) linkOptions() {
|
||||
f.onClick(options, "2.8", f.setDriver(kbd)) // Keyboard settings button
|
||||
|
||||
f.configureSlider(options, "2.9", h3Slider) // Resolution slider
|
||||
f.configureSlider(options, "2.10", v10Slider) // Music volume slider
|
||||
f.configureSlider(options, "2.11", v10Slider) // SFX volume slider
|
||||
|
||||
f.onClick(options, "2.12", f.acceptOptions) // OK button
|
||||
f.onClick(options, "2.24", f.cancelOptions) // Cancel button
|
||||
|
||||
f.configureSlider(options, "2.26", h9Slider) // Unit speed slider
|
||||
f.configureSlider(options, "2.27", h9Slider) // Animation speed slider
|
||||
|
||||
// Keyboard settings
|
||||
// TODO: implement keybindings save/load behaviour
|
||||
f.onClick(kbd, "3.1", f.setDriver(options)) // Done button
|
||||
f.onClick(kbd, "3.2", f.setDriver(options)) // Cancel button
|
||||
f.onClick(kbd, "3.4", func() {}) // TODO: Reset to defaults button
|
||||
}
|
||||
|
||||
// FIXME: exiting is a bit OTT. Perhaps display "save failed"?
|
||||
func (f *Flow) acceptOptions() {
|
||||
if err := f.optionsIntoConfig(); err != nil {
|
||||
log.Printf("Saving options to config failed: %v", err)
|
||||
f.exit = err
|
||||
} else {
|
||||
f.setDriverNow(main)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: again, exiting is OTT. We're just resetting the state of
|
||||
// the interface to the values in config.
|
||||
func (f *Flow) cancelOptions() {
|
||||
if err := f.configIntoOptions(); err != nil {
|
||||
log.Printf("Saving options to config failed: %v", err)
|
||||
f.exit = err
|
||||
} else {
|
||||
f.exit = f.returnToLastDriverNow(options)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flow) configIntoOptions() error {
|
||||
var err error
|
||||
|
||||
cfg := &f.config.Options
|
||||
optionsUI := f.drivers[options]
|
||||
|
||||
try(optionsUI.SetValueBool("2.1", cfg.PlayMovies), &err)
|
||||
try(optionsUI.SetValueBool("2.1", cfg.Animations), &err)
|
||||
try(optionsUI.SetValueBool("2.3", cfg.PlayMusic), &err)
|
||||
try(optionsUI.SetValueBool("2.4", cfg.CombatVoices), &err)
|
||||
try(optionsUI.SetValueBool("2.5", cfg.ShowGrid), &err)
|
||||
try(optionsUI.SetValueBool("2.6", cfg.ShowPaths), &err)
|
||||
try(optionsUI.SetValueBool("2.7", cfg.PointSaving), &err)
|
||||
try(optionsUI.SetValueInt("2.9", cfg.ResolutionIndex()), &err)
|
||||
try(optionsUI.SetValueInt("2.10", cfg.MusicVolume), &err)
|
||||
try(optionsUI.SetValueInt("2.11", cfg.SFXVolume), &err)
|
||||
try(optionsUI.SetValueBool("2.25", cfg.AutoCutLevel), &err)
|
||||
try(optionsUI.SetValueInt("2.26", cfg.UnitSpeed), &err)
|
||||
try(optionsUI.SetValueInt("2.27", cfg.AnimSpeed), &err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Flow) optionsIntoConfig() error {
|
||||
var resIdx int // needs handling manually
|
||||
var err error
|
||||
|
||||
cfg := &f.config.Options
|
||||
optionsUI := f.drivers[options]
|
||||
|
||||
try(optionsUI.ValueBool("2.1", &cfg.PlayMovies), &err)
|
||||
try(optionsUI.ValueBool("2.2", &cfg.Animations), &err)
|
||||
try(optionsUI.ValueBool("2.3", &cfg.PlayMusic), &err)
|
||||
try(optionsUI.ValueBool("2.4", &cfg.CombatVoices), &err)
|
||||
try(optionsUI.ValueBool("2.5", &cfg.ShowGrid), &err)
|
||||
try(optionsUI.ValueBool("2.6", &cfg.ShowPaths), &err)
|
||||
try(optionsUI.ValueBool("2.7", &cfg.PointSaving), &err)
|
||||
try(optionsUI.ValueInt("2.9", &resIdx), &err)
|
||||
try(optionsUI.ValueInt("2.10", &cfg.MusicVolume), &err)
|
||||
try(optionsUI.ValueInt("2.11", &cfg.SFXVolume), &err)
|
||||
try(optionsUI.ValueBool("2.25", &cfg.AutoCutLevel), &err)
|
||||
try(optionsUI.ValueInt("2.26", &cfg.UnitSpeed), &err)
|
||||
try(optionsUI.ValueInt("2.27", &cfg.AnimSpeed), &err)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.SetResolutionIndex(resIdx)
|
||||
|
||||
if err := f.config.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func try(result error, into *error) {
|
||||
if *into == nil {
|
||||
*into = result
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user