We can reach the bridge \o/

This commit is contained in:
2020-03-26 23:35:34 +00:00
parent e4ce932324
commit 79bfab7d6b
9 changed files with 230 additions and 69 deletions

View File

@@ -40,6 +40,30 @@ const (
TypeDialogue MenuType = 300 TypeDialogue MenuType = 300
) )
// FIXME: certain elements - especially overlays - don't have a DESC specified
// in the .mnu file, but display text specified with a number in i18n. The only
// conclusion I can draw is that they're hardcoded in the binary and set from
// outside. So, do that here.
var DescOverrides = map[string]map[string]int{
"main": {
"2.6": 50992,
},
"newgame": {
"2.5": 50993,
},
"levelply": {
"2.6": 50996,
},
}
// FIXME: Same idea with text overrides, only these aren't mentioned in the .dta
// file at all!
var TextOverrides = map[string]map[string]string{
"main": {
"2.7": "0.1-ordoor",
},
}
type Record struct { type Record struct {
Menu *Menu Menu *Menu
Parent *Record Parent *Record
@@ -56,8 +80,8 @@ type Record struct {
Y int Y int
// From i18n // From i18n
Text string Text string
Help string Help string
// FIXME: turn these into first-class data // FIXME: turn these into first-class data
properties map[string]string properties map[string]string
@@ -239,13 +263,16 @@ type Replacer interface {
} }
func (r *Record) Internationalize(replacer Replacer) { func (r *Record) Internationalize(replacer Replacer) {
// FIXME: I can't see how else this would happen in the original if overrides, ok := TextOverrides[r.Menu.Name]; ok {
if r.Menu.Name == "main" { if override, ok := overrides[r.Path()]; ok {
switch r.Path() { delete(r.properties, "DESC")
case "2.6": r.Text = override
r.properties["DESC"] = "50992" // Chaos Gate }
case "2.7": }
r.Text = "ordoor-0.0.0"
if overrides, ok := DescOverrides[r.Menu.Name]; ok {
if override, ok := overrides[r.Path()]; ok {
r.properties["DESC"] = strconv.Itoa(override)
} }
} }

View File

@@ -34,6 +34,7 @@ const (
loadGame driverName = "loadGame" loadGame driverName = "loadGame"
options driverName = "options" options driverName = "options"
kbd driverName = "keyboard" kbd driverName = "keyboard"
bridge driverName = "bridge"
) )
var ( var (
@@ -41,6 +42,7 @@ var (
driverNames = []driverName{ driverNames = []driverName{
main, levelPly, singles, randomMap, newGame, loadGame, options, kbd, main, levelPly, singles, randomMap, newGame, loadGame, options, kbd,
bridge,
} }
// Constants used for sliders // Constants used for sliders
@@ -88,6 +90,20 @@ func New(assets *assetstore.AssetStore, config *config.Config) (*Flow, error) {
return out, nil 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(menu)
if err != nil {
return nil, err
}
return driver, nil
}
func (f *Flow) Update(screenX, screenY int) error { func (f *Flow) Update(screenX, screenY int) error {
if f.exit != nil { if f.exit != nil {
return f.exit return f.exit
@@ -104,48 +120,19 @@ func (f *Flow) Draw(screen *ebiten.Image) error {
return f.current.Draw(screen) return f.current.Draw(screen)
} }
func (f *Flow) setDriver(name driverName) func() {
return func() {
f.setDriverNow(name)
}
}
func (f *Flow) setDriverNow(name driverName) {
f.current = f.drivers[name]
}
func (f *Flow) setExit() {
f.exit = ErrExit
}
func (f *Flow) linkDrivers() { func (f *Flow) linkDrivers() {
// Main interface // linkMain
f.onClick(main, "2.1", f.setDriver(newGame)) // New game f.onClick(main, "2.1", f.setDriver(newGame)) // New game
f.onClick(main, "2.2", f.setDriver(loadGame)) // Load game f.onClick(main, "2.2", f.setDriver(loadGame)) // Load game
f.setFreeze(main, "2.3", true) // Multiplayer - disable for now f.setFreeze(main, "2.3", true) // Multiplayer - disable for now
f.onClick(main, "2.4", f.setDriver(options)) // Options f.onClick(main, "2.4", f.setDriver(options)) // Options
f.onClick(main, "2.5", f.setExit) // Quit f.onClick(main, "2.5", f.setExit) // Quit
// New game // Now link immediate children. They will link their children, and so on
f.onClick(newGame, "2.1", f.setDriver(levelPly)) // New campaign button f.linkNewGame()
f.onClick(newGame, "2.2", f.setDriver(singles)) // Single scenario button f.linkLoadGame()
f.onClick(newGame, "2.3", f.setDriver(randomMap)) // Random scenario button // TODO: link multiplayer
f.onClick(newGame, "2.4", f.setDriver(main)) // Back button
// Load game
f.onClick(loadGame, "3.3", f.setDriver(main)) // Cancel button
// Options
f.linkOptions() f.linkOptions()
// Level of play select
f.onClick(levelPly, "2.5", f.setDriver(newGame)) // Back button
// Single scenario setup
f.onClick(singles, "4.11", f.setDriver(newGame)) // Back button
// Random map setup
f.onClick(randomMap, "2.19", f.setDriver(newGame)) // Back button
} }
func (f *Flow) configureSlider(driver driverName, id string, steps map[int]int) { func (f *Flow) configureSlider(driver driverName, id string, steps map[int]int) {
@@ -172,16 +159,24 @@ func (f *Flow) setFreeze(driver driverName, id string, value bool) {
f.exit = f.drivers[driver].SetFreeze(id, value) f.exit = f.drivers[driver].SetFreeze(id, value)
} }
func buildDriver(assets *assetstore.AssetStore, name driverName) (*ui.Driver, error) { func (f *Flow) setValueBool(driver driverName, id string, value bool) {
menu, err := assets.Menu(string(name)) if f.exit != nil {
if err != nil { return
return nil, err
} }
driver, err := ui.NewDriver(menu) f.exit = f.drivers[driver].SetValueBool(id, value)
if err != nil { }
return nil, err
} func (f *Flow) setDriver(name driverName) func() {
return func() {
return driver, nil f.setDriverNow(name)
}
}
func (f *Flow) setDriverNow(name driverName) {
f.current = f.drivers[name]
}
func (f *Flow) setExit() {
f.exit = ErrExit
} }

View File

@@ -0,0 +1,6 @@
package flow
func (f *Flow) linkLoadGame() {
// Load game
f.onClick(loadGame, "3.3", f.setDriver(main)) // Cancel button
}

View File

@@ -0,0 +1,41 @@
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 Here 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
}
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
}

View File

@@ -5,7 +5,6 @@ import (
) )
func (f *Flow) linkOptions() { func (f *Flow) linkOptions() {
// Main options
f.onClick(options, "2.8", f.setDriver(kbd)) // Keyboard settings button f.onClick(options, "2.8", f.setDriver(kbd)) // Keyboard settings button
f.configureSlider(options, "2.9", h3Slider) // Resolution slider f.configureSlider(options, "2.9", h3Slider) // Resolution slider

View File

@@ -91,6 +91,7 @@ func Run(configFile string, overrideX, overrideY int) error {
} }
func (o *Ordoor) Run() error { func (o *Ordoor) Run() error {
// FIXME: we're missing a screen about SSI here
if o.config.Options.PlayMovies { if o.config.Options.PlayMovies {
o.PlayUnskippableVideo("LOGOS") o.PlayUnskippableVideo("LOGOS")
o.PlaySkippableVideo("movie1") o.PlaySkippableVideo("movie1")

View File

@@ -21,36 +21,35 @@ func init() {
// FIXME: these need implementing // FIXME: these need implementing
// Needed for Keyboard.mnu (main -> options -> keyboard) // Needed for Keyboard.mnu (main -> options -> keyboard)
registerBuilder(menus.TypeLineKbd, nil) registerBuilder(menus.TypeLineKbd, registerDebug("Unimplemented LineKbd", nil))
registerBuilder(menus.TypeDialogue, nil) registerBuilder(menus.TypeDialogue, registerDebug("Unimplemented Dialogue", nil))
// Needed for Arrange.mnu (???) // Needed for Arrange.mnu (???)
registerBuilder(menus.TypeSquadButton, nil) registerBuilder(menus.TypeSquadButton, registerDebug("Unimplemented SquadButton", nil))
registerBuilder(menus.TypeAnimationToo, nil) registerBuilder(menus.TypeAnimationToo, registerDebug("Unimplemented AnimationToo", nil))
// Needed for Bridge.mnu // Needed for Bridge.mnu
registerBuilder(menus.TypeDoorHotspot, nil) registerBuilder(menus.TypeDoorHotspot, registerDebug("Unimplemented DoorHotspot", nil))
// Needed for Briefing.mnu // Needed for Briefing.mnu
registerBuilder(menus.TypeLineBriefing, nil) registerBuilder(menus.TypeLineBriefing, registerDebug("Unimplemented LineBriefing", nil))
// Needed for ChaEquip.mnu // Needed for ChaEquip.mnu
registerBuilder(menus.TypeUnknown1, nil) registerBuilder(menus.TypeUnknown1, registerDebug("Unimplemented Unknown1", nil))
registerBuilder(menus.TypeThumb, nil) registerBuilder(menus.TypeThumb, registerDebug("Unimplemented Thumb", nil))
registerBuilder(menus.TypeInventorySelect, nil)
// Needed for MainGameChaos.mnu // Needed for MainGameChaos.mnu
registerBuilder(menus.TypeStatusBar, nil) registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil))
// Needed for Multiplayer_Choose.mnu // Needed for Multiplayer_Choose.mnu
registerBuilder(menus.TypeComboBoxItem, nil) registerBuilder(menus.TypeComboBoxItem, registerDebug("Unimplemented ComboBoxItem", nil))
registerBuilder(menus.TypeDropdownButton, nil) registerBuilder(menus.TypeDropdownButton, registerDebug("Unimplemented DropdownButton", nil))
// Needed for Multiplayer_Configure.mnu // Needed for Multiplayer_Configure.mnu
registerBuilder(menus.TypeEditBox, nil) registerBuilder(menus.TypeEditBox, registerDebug("Unimplemented EditBox", nil))
// Needed for Multiplayer_Connect.mnu // Needed for Multiplayer_Connect.mnu
registerBuilder(menus.TypeRadioButton, nil) registerBuilder(menus.TypeRadioButton, registerDebug("Unimplemented RadioButton", nil))
} }
const ( const (
@@ -66,6 +65,19 @@ var (
// Used to add widgets to a driver // Used to add widgets to a driver
type builderFunc func(d *Driver, r *menus.Record) error type builderFunc func(d *Driver, r *menus.Record) error
func registerDebug(reason string, onward builderFunc) builderFunc {
return func(d *Driver, r *menus.Record) error {
log.Printf("%v: %#+v", reason, r)
if onward == nil {
return registerStatic(d, r)
} else {
return onward(d, r)
}
return nil
}
}
func registerBuilder(t menus.MenuType, f builderFunc) { func registerBuilder(t menus.MenuType, f builderFunc) {
if _, ok := widgetBuilders[t]; ok { if _, ok := widgetBuilders[t]; ok {
panic(fmt.Sprintf("A builder for menu type %v already exists", t)) panic(fmt.Sprintf("A builder for menu type %v already exists", t))
@@ -187,7 +199,6 @@ func (d *Driver) ConfigureSlider(id string, steps map[int]int) error {
for _, clickable := range d.clickables { for _, clickable := range d.clickables {
if slider, ok := clickable.(*slider); ok && slider.id() == id { if slider, ok := clickable.(*slider); ok && slider.id() == id {
slider.steps = steps slider.steps = steps
log.Printf("Found slider %#+v", slider)
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package ui
import ( import (
"image" "image"
"log"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/ebitenutil"
@@ -91,6 +92,8 @@ func registerOverlay(d *Driver, r *menus.Record) error {
ebitenutil.DebugPrint(textImg, r.Text) ebitenutil.DebugPrint(textImg, r.Text)
ni.textImg = textImg ni.textImg = textImg
} else {
log.Printf("Overlay without text detected: %#+v", r)
} }
d.paintables = append(d.paintables, ni) d.paintables = append(d.paintables, ni)

View File

@@ -12,6 +12,7 @@ import (
func init() { func init() {
registerBuilder(menus.TypeCheckbox, registerCheckbox) registerBuilder(menus.TypeCheckbox, registerCheckbox)
registerBuilder(menus.TypeSlider, registerSlider) registerBuilder(menus.TypeSlider, registerSlider)
registerBuilder(menus.TypeInventorySelect, registerInventorySelect)
} }
// A checkbox can be a fancy button // A checkbox can be a fancy button
@@ -37,6 +38,15 @@ type slider struct {
valueImpl valueImpl
} }
// An inventory select is a sort of radio button. If 2 share the same menu,
// selecting one deselects the other. Otherwise, they act like checkboxes
type inventorySelect struct {
checkbox
parentPath string
others []*inventorySelect
}
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled. // A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
func registerCheckbox(d *Driver, r *menus.Record) error { func registerCheckbox(d *Driver, r *menus.Record) error {
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
@@ -86,6 +96,60 @@ func registerSlider(d *Driver, r *menus.Record) error {
return nil return nil
} }
func registerInventorySelect(d *Driver, r *menus.Record) error {
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, checked, disabled
if err != nil {
return err
}
element := &inventorySelect{
checkbox: checkbox{
button: button{
path: r.Path(),
baseSpr: sprites[0], // unchecked
clickSpr: sprites[1], // checked
frozenSpr: sprites[2], // disabled
hoverImpl: hoverImpl{text: r.Text},
},
valueImpl: valueImpl{str: "0"},
},
}
d.clickables = append(d.clickables, element)
d.freezables = append(d.freezables, element)
d.hoverables = append(d.hoverables, element)
d.paintables = append(d.paintables, element)
d.valueables = append(d.valueables, element)
if r.Parent == nil {
return nil
}
element.parentPath = r.Parent.Path()
// Now update all inventory selects belonging to the same menu so they share
// a list of all inventory selects. This will be replaced several times as
// the menu is built.
var inventorySelects []*inventorySelect
for _, valueable := range d.valueables {
if is, ok := valueable.(*inventorySelect); ok && is.parentPath == element.parentPath {
inventorySelects = append(inventorySelects, is)
}
}
for _, is := range inventorySelects {
is.others = inventorySelects
}
// Select the first in the list
if len(inventorySelects) == 1 {
element.setValue("1")
}
return nil
}
func (c *checkbox) registerMouseClick() { func (c *checkbox) registerMouseClick() {
if c.value() == "1" { // Click disables if c.value() == "1" { // Click disables
c.setValue("0") c.setValue("0")
@@ -254,3 +318,17 @@ func (s *slider) valueInt() int {
func (s *slider) value() string { func (s *slider) value() string {
return strconv.Itoa(s.valueInt()) return strconv.Itoa(s.valueInt())
} }
func (i *inventorySelect) registerMouseClick() {
// Do nothing if we're already selected
if i.value() == "1" {
return
}
// Turn us on, turn everyone else off
for _, other := range i.others {
other.setValue("0")
}
i.setValue("1")
}