diff --git a/internal/menus/menus.go b/internal/menus/menus.go index f94fbca..5f46987 100644 --- a/internal/menus/menus.go +++ b/internal/menus/menus.go @@ -17,13 +17,13 @@ const ( TypeMenu MenuType = 1 TypeDragMenu MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra} TypeSimpleButton MenuType = 3 - TypeDoorHotspot MenuType = 30 // Like a button I guess? FONTTYPE is animation speed - TypeUnknown1 MenuType = 31 // ??? Needed for ChaEquip.mnu + TypeDoorHotspot MenuType = 30 // Like a button I guess? "FONTTYPE is animation speed" + TypeDoorHotspot2 MenuType = 31 // Seems like a duplicate of the above? What's different? TypeLineKbd MenuType = 40 TypeThumb MenuType = 45 TypeLineBriefing MenuType = 41 TypeInvokeButton MenuType = 50 - TypeSquadButton MenuType = 60 // Maybe? Appears in Arrange.mnu + TypeDoorHotspot3 MenuType = 60 // Maybe? Appears in Arrange.mnu TypeOverlay MenuType = 61 TypeHypertext MenuType = 70 TypeCheckbox MenuType = 91 diff --git a/internal/ordoor/flow/bridge.go b/internal/ordoor/flow/bridge.go index 9dadf08..4377963 100644 --- a/internal/ordoor/flow/bridge.go +++ b/internal/ordoor/flow/bridge.go @@ -3,9 +3,40 @@ package flow func (f *Flow) linkBridge() { // FIXME: sometimes these doors are frozen, depending on game state - f.onClick(bridge, "2.1", f.setDriver(briefing)) // 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.onClick(bridge, "2.6", f.setDriver(configureVehiclesUltra)) // Vehicle configure door hotspot - f.onClick(bridge, "2.8", f.setDriver(configureUltEquip)) // Squads configure door hotspot + //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.linkChoices() + f.linkArrange() +} + +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 } diff --git a/internal/ordoor/flow/flow.go b/internal/ordoor/flow/flow.go index dc792c6..0e1dbf5 100644 --- a/internal/ordoor/flow/flow.go +++ b/internal/ordoor/flow/flow.go @@ -2,6 +2,7 @@ package flow import ( "errors" + "fmt" "github.com/hajimehoshi/ebiten" @@ -19,6 +20,13 @@ type Flow struct { 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 } @@ -37,10 +45,12 @@ const ( bridge driverName = "Bridge" briefing driverName = "Briefing" choices driverName = "Choices" - ultEquip driverName = "UltEquip" + saveGame driverName = "SaveGame" + credits driverName = "Credits" + arrange driverName = "Arrange" configureUltEquip driverName = "Configure_UltEquip" - configureVehiclesUltra driverName = "Configure_vehicles_ultra" + configureVehiclesUltra driverName = "Configure_Vehicles_Ultra" ) var ( @@ -48,8 +58,8 @@ var ( driverNames = []driverName{ main, levelPly, singles, randomMap, newGame, loadGame, options, kbd, - bridge, briefing, choices, ultEquip, - configureVehiclesUltra, + bridge, briefing, choices, saveGame, credits, arrange, + configureUltEquip, configureVehiclesUltra, } // Constants used for sliders @@ -74,6 +84,7 @@ func New(assets *assetstore.AssetStore, config *config.Config) (*Flow, error) { assets: assets, config: config, drivers: make(map[driverName]*ui.Driver, len(driverNames)), + returns: make(map[driverName]driverName), } // Load all the drivers upfront @@ -129,11 +140,11 @@ func (f *Flow) Draw(screen *ebiten.Image) error { 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.setDriver(options)) // Options - f.onClick(main, "2.5", f.setExit) // Quit + 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() @@ -142,6 +153,14 @@ func (f *Flow) linkDrivers() { 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 @@ -155,7 +174,7 @@ func (f *Flow) onClick(driver driverName, id string, fn func()) { return } - f.exit = f.drivers[driver].OnClick(id, fn) + f.exit = maybeErr(driver, f.drivers[driver].OnClick(id, fn)) } func (f *Flow) setFreeze(driver driverName, id string, value bool) { @@ -163,7 +182,7 @@ func (f *Flow) setFreeze(driver driverName, id string, value bool) { return } - f.exit = f.drivers[driver].SetFreeze(id, value) + f.exit = maybeErr(driver, f.drivers[driver].SetFreeze(id, value)) } func (f *Flow) setValueBool(driver driverName, id string, value bool) { @@ -184,6 +203,27 @@ 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 } diff --git a/internal/ordoor/flow/new_game.go b/internal/ordoor/flow/new_game.go index 1b46dc9..65e72a6 100644 --- a/internal/ordoor/flow/new_game.go +++ b/internal/ordoor/flow/new_game.go @@ -26,10 +26,13 @@ func (f *Flow) linkLevelPly() { f.setDriverNow(newGame) }) - // FIXME: we should select a savegame if Mighty Here is selected here + // 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() { diff --git a/internal/ordoor/flow/options.go b/internal/ordoor/flow/options.go index e146251..33c5173 100644 --- a/internal/ordoor/flow/options.go +++ b/internal/ordoor/flow/options.go @@ -44,7 +44,7 @@ func (f *Flow) cancelOptions() func() { log.Printf("Saving options to config failed: %v", err) f.exit = err } else { - f.setDriverNow(main) + f.exit = f.returnToLastDriverNow(options) } } } diff --git a/internal/ui/buttons.go b/internal/ui/buttons.go index 6724cb9..cc79da5 100644 --- a/internal/ui/buttons.go +++ b/internal/ui/buttons.go @@ -11,7 +11,9 @@ func init() { registerBuilder(menus.TypeSimpleButton, registerSimpleButton) registerBuilder(menus.TypeInvokeButton, registerInvokeButton) registerBuilder(menus.TypeMainButton, registerMainButton) - registerBuilder(menus.TypeDoorHotspot, registerDebug("Unimplemented DoorHotspot", nil)) + registerBuilder(menus.TypeDoorHotspot, registerDoorHotspot) + registerBuilder(menus.TypeDoorHotspot2, registerDoorHotspot) + registerBuilder(menus.TypeDoorHotspot3, registerDoorHotspot) } // A button without hover animation @@ -79,6 +81,29 @@ func registerMainButton(d *Driver, r *menus.Record) error { return nil } +func registerDoorHotspot(d *Driver, r *menus.Record) error { + sprites, err := d.menu.Sprites(r.Share, 2) // base, pressed + if err != nil { + return err + } + + btn := &button{ + path: r.Path(), + baseSpr: sprites[0], + clickSpr: sprites[1], + frozenSpr: sprites[0], // No disabled sprite + hoverImpl: hoverImpl{text: r.Text}, + } + + d.clickables = append(d.clickables, btn) + d.freezables = append(d.freezables, btn) + d.hoverables = append(d.hoverables, btn) + d.paintables = append(d.paintables, btn) + + return nil + +} + func registerButton(d *Driver, r *menus.Record, spriteId int) error { sprites, err := d.menu.Sprites(spriteId, 3) // base, pressed, disabled if err != nil { diff --git a/internal/ui/driver.go b/internal/ui/driver.go index 486a22d..77f007f 100644 --- a/internal/ui/driver.go +++ b/internal/ui/driver.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "log" + "runtime/debug" "strconv" "github.com/hajimehoshi/ebiten" @@ -24,14 +25,10 @@ func init() { registerBuilder(menus.TypeLineKbd, registerDebug("Unimplemented LineKbd", nil)) registerBuilder(menus.TypeDialogue, registerDebug("Unimplemented Dialogue", nil)) - // Needed for Arrange.mnu (???) - registerBuilder(menus.TypeSquadButton, registerDebug("Unimplemented SquadButton", nil)) - // Needed for Briefing.mnu registerBuilder(menus.TypeLineBriefing, registerDebug("Unimplemented LineBriefing", nil)) // Needed for ChaEquip.mnu - registerBuilder(menus.TypeUnknown1, registerDebug("Unimplemented Unknown1", nil)) registerBuilder(menus.TypeThumb, registerDebug("Unimplemented Thumb", nil)) // Needed for MainGameChaos.mnu @@ -226,6 +223,11 @@ func (d *Driver) SetValueInt(id string, value int) error { } func (d *Driver) Update(screenX, screenY int) error { + if d == nil { + debug.PrintStack() + return fmt.Errorf("Tried to update a nil ui.Driver") + } + // This will be updated while processing hovers d.tooltip = "" d.ticks += 1 @@ -274,6 +276,11 @@ func (d *Driver) Update(screenX, screenY int) error { } func (d *Driver) Draw(screen *ebiten.Image) error { + if d == nil { + debug.PrintStack() + return fmt.Errorf("Tried to draw a nil ui.Driver") + } + var do ebiten.DrawImageOptions for _, paint := range d.paintables { diff --git a/internal/ui/noninteractive.go b/internal/ui/noninteractive.go index c09dd75..1286510 100644 --- a/internal/ui/noninteractive.go +++ b/internal/ui/noninteractive.go @@ -15,7 +15,7 @@ func init() { registerBuilder(menus.TypeHypertext, registerHypertext) registerBuilder(menus.TypeOverlay, registerOverlay) registerBuilder(menus.TypeAnimationSample, registerAnimation) - registerBuilder(menus.TypeAnimationHover, registerDebug("WIP AnimationHover", registerAnimationHover)) + registerBuilder(menus.TypeAnimationHover, registerAnimationHover) } // A non-interactive element is not a widget; it merely displays some pixels and