This is pretty awful, but will let me wire up items more easily without needing to do the big refactor into independent menu handlers
322 lines
7.0 KiB
Go
322 lines
7.0 KiB
Go
package flow
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"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
|
|
}
|
|
|
|
var (
|
|
ErrExit = errors.New("exiting gracefully")
|
|
|
|
// 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
|
|
}
|
|
|
|
out.linkDrivers()
|
|
out.reset()
|
|
|
|
return out, out.exit
|
|
}
|
|
|
|
func (f *Flow) SetScenario(scenario *scenario.Scenario) {
|
|
f.current = f.drivers[mainGame]
|
|
f.scenario = scenario
|
|
}
|
|
|
|
func (f *Flow) Update(screenX, screenY int) error {
|
|
if f.exit != nil {
|
|
return f.exit
|
|
}
|
|
|
|
// Keybindings for map control
|
|
// FIXME: this needs a big rethink
|
|
if f.current != nil && f.scenario != nil && !f.current.IsInDialogue() {
|
|
step := 32
|
|
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
|
|
f.scenario.Viewpoint.X -= step
|
|
}
|
|
if ebiten.IsKeyPressed(ebiten.KeyRight) {
|
|
f.scenario.Viewpoint.X += step
|
|
}
|
|
if ebiten.IsKeyPressed(ebiten.KeyUp) {
|
|
f.scenario.Viewpoint.Y -= step
|
|
}
|
|
if ebiten.IsKeyPressed(ebiten.KeyDown) {
|
|
f.scenario.Viewpoint.Y += step
|
|
}
|
|
}
|
|
|
|
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.setReturningDriver(main, newGame)) // New game
|
|
f.onClick(main, "2.2", f.setReturningDriver(main, 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) valueBool(driver driverName, id string) bool {
|
|
if f.exit != nil {
|
|
return false
|
|
}
|
|
|
|
var value bool
|
|
|
|
f.exit = f.drivers[driver].ValueBool(id, &value)
|
|
|
|
return value
|
|
}
|
|
|
|
func (f *Flow) playNextScenario(from driverName) 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.setReturningDriverNow(from, mainGame)
|
|
f.scenario = scenario
|
|
}
|
|
}
|
|
|
|
func (f *Flow) setActive(driver driverName, id string, value bool) func() {
|
|
return func() {
|
|
if f.exit != nil {
|
|
return
|
|
}
|
|
|
|
f.exit = maybeErr(driver, f.setActiveNow(driver, id, value))
|
|
}
|
|
}
|
|
|
|
func (f *Flow) setActiveNow(driver driverName, id string, value bool) error {
|
|
return f.drivers[driver].SetActive(locator(driver, id), value)
|
|
}
|
|
|
|
func (f *Flow) toggleActive(driver driverName, id string) func() {
|
|
return func() {
|
|
if f.exit != nil {
|
|
return
|
|
}
|
|
|
|
f.exit = maybeErr(driver, f.drivers[driver].ToggleActive(locator(driver, id)))
|
|
}
|
|
}
|
|
|
|
func (f *Flow) showDialogue(driver driverName, id string) func() {
|
|
return func() {
|
|
if f.exit != nil {
|
|
return
|
|
}
|
|
|
|
f.exit = maybeErr(driver, f.drivers[driver].ShowDialogue(locator(driver, id)))
|
|
}
|
|
}
|
|
|
|
func (f *Flow) hideDialogue(driver driverName) func() {
|
|
return f.drivers[driver].HideDialogue
|
|
}
|
|
|
|
func (f *Flow) withScenario(then func()) func() {
|
|
return func() {
|
|
if f.scenario != nil {
|
|
then()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Flow) reset() {
|
|
if f.exit != nil {
|
|
return
|
|
}
|
|
|
|
f.setDriverNow(main) // Back to the main interface
|
|
|
|
// Wipe out any returns that may exist
|
|
f.returns = make(map[driverName]driverName)
|
|
|
|
// FIXME: these should really happen via data binding.
|
|
f.resetLevelPlyInventorySelect()
|
|
f.exit = f.configIntoOptions()
|
|
}
|
|
|
|
func (f *Flow) setExit() {
|
|
f.exit = ErrExit
|
|
}
|
|
|
|
// TODO: convert all to locators
|
|
func locator(driver driverName, id string) string {
|
|
return fmt.Sprintf("%v:%v", strings.ToLower(string(driver)), id)
|
|
}
|