2020-03-25 02:12:17 +00:00
|
|
|
package flow
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2020-03-27 02:07:28 +00:00
|
|
|
"fmt"
|
2020-04-11 00:13:28 +01:00
|
|
|
"log"
|
2020-04-14 12:12:37 +01:00
|
|
|
"strings"
|
2020-03-25 02:12:17 +00:00
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten"
|
|
|
|
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/config"
|
2020-04-11 00:13:28 +01:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/data"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/scenario"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/ship"
|
2020-03-25 02:12:17 +00:00
|
|
|
"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
|
2020-04-11 00:13:28 +01:00
|
|
|
generic *data.Generic
|
2020-03-25 02:12:17 +00:00
|
|
|
|
2020-03-27 02:07:28 +00:00
|
|
|
// 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
|
|
|
|
|
2020-04-11 00:13:28 +01:00
|
|
|
// If we're currently playing a scenario, it it placed here
|
|
|
|
scenario *scenario.Scenario
|
|
|
|
|
|
|
|
ship *ship.Ship
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-04-11 00:13:28 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
out := &Flow{
|
|
|
|
assets: assets,
|
|
|
|
config: config,
|
2020-04-11 00:13:28 +01:00
|
|
|
generic: generic,
|
2020-03-25 02:12:17 +00:00
|
|
|
drivers: make(map[driverName]*ui.Driver, len(driverNames)),
|
2020-03-27 02:07:28 +00:00
|
|
|
returns: make(map[driverName]driverName),
|
2020-04-11 00:13:28 +01:00
|
|
|
ship: ship,
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
2020-04-14 15:11:25 +01:00
|
|
|
out.reset()
|
2020-03-25 02:12:17 +00:00
|
|
|
|
2020-04-14 15:11:25 +01:00
|
|
|
return out, out.exit
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Flow) Update(screenX, screenY int) error {
|
|
|
|
if f.exit != nil {
|
|
|
|
return f.exit
|
|
|
|
}
|
|
|
|
|
2020-04-20 00:16:21 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-11 00:13:28 +01:00
|
|
|
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
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Flow) Draw(screen *ebiten.Image) error {
|
|
|
|
if f.exit != nil {
|
|
|
|
return f.exit
|
|
|
|
}
|
|
|
|
|
2020-04-11 00:13:28 +01:00
|
|
|
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
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 20:54:58 +01:00
|
|
|
func (f *Flow) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
2020-04-11 00:13:28 +01:00
|
|
|
if f.current != nil {
|
|
|
|
return f.current.Cursor()
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: we should get a cursor from current all the time.
|
|
|
|
return nil, nil, nil
|
2020-04-10 20:54:58 +01:00
|
|
|
}
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
func (f *Flow) linkDrivers() {
|
2020-03-26 23:35:34 +00:00
|
|
|
// linkMain
|
2020-04-14 15:11:25 +01:00
|
|
|
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
|
2020-03-25 02:12:17 +00:00
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
// Now link immediate children. They will link their children, and so on
|
|
|
|
f.linkNewGame()
|
|
|
|
f.linkLoadGame()
|
|
|
|
// TODO: link multiplayer
|
2020-03-25 02:12:17 +00:00
|
|
|
f.linkOptions()
|
|
|
|
}
|
|
|
|
|
2020-03-27 02:07:28 +00:00
|
|
|
func maybeErr(driver driverName, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%v: %v", driver, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-27 02:07:28 +00:00
|
|
|
f.exit = maybeErr(driver, f.drivers[driver].OnClick(id, fn))
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Flow) setFreeze(driver driverName, id string, value bool) {
|
|
|
|
if f.exit != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-27 02:07:28 +00:00
|
|
|
f.exit = maybeErr(driver, f.drivers[driver].SetFreeze(id, value))
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
func (f *Flow) setValueBool(driver driverName, id string, value bool) {
|
|
|
|
if f.exit != nil {
|
|
|
|
return
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
f.exit = f.drivers[driver].SetValueBool(id, value)
|
|
|
|
}
|
|
|
|
|
2020-04-14 15:11:25 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:01:05 +01:00
|
|
|
func (f *Flow) playNextScenario(from driverName) func() {
|
2020-04-11 00:13:28 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:01:05 +01:00
|
|
|
f.setReturningDriverNow(from, mainGame)
|
2020-04-11 00:13:28 +01:00
|
|
|
f.scenario = scenario
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-19 18:21:08 +01:00
|
|
|
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)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-14 12:12:37 +01:00
|
|
|
func (f *Flow) showDialogue(driver driverName, id string) func() {
|
|
|
|
return func() {
|
2020-04-19 18:21:08 +01:00
|
|
|
if f.exit != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
f.exit = maybeErr(driver, f.drivers[driver].ShowDialogue(locator(driver, id)))
|
2020-04-14 12:12:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Flow) hideDialogue(driver driverName) func() {
|
|
|
|
return f.drivers[driver].HideDialogue
|
|
|
|
}
|
|
|
|
|
2020-04-20 00:16:21 +01:00
|
|
|
func (f *Flow) withScenario(then func()) func() {
|
|
|
|
return func() {
|
|
|
|
if f.scenario != nil {
|
|
|
|
then()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-14 15:11:25 +01:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
func (f *Flow) setExit() {
|
|
|
|
f.exit = ErrExit
|
2020-03-25 02:12:17 +00:00
|
|
|
}
|
2020-04-14 12:12:37 +01:00
|
|
|
|
|
|
|
// TODO: convert all to locators
|
2020-04-14 15:11:25 +01:00
|
|
|
func locator(driver driverName, id string) string {
|
|
|
|
return fmt.Sprintf("%v:%v", strings.ToLower(string(driver)), id)
|
2020-04-14 12:12:37 +01:00
|
|
|
}
|