Allow dialogues to be hidden or shown
To do this, MENU and SUBMENU are split into two types (at last), and a Widget type is introduced. This should allow lots of code to be removed at some point.
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -13,38 +12,44 @@ import (
|
||||
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
||||
)
|
||||
|
||||
// MenuType tells us what sort of Group we have
|
||||
type MenuType int
|
||||
|
||||
// SubMenuType tells us what sort of Record we have
|
||||
type SubMenuType int
|
||||
|
||||
const (
|
||||
TypeStatic MenuType = 0
|
||||
TypeMenu MenuType = 1
|
||||
TypeDragMenu MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra}
|
||||
TypeStatic MenuType = 0
|
||||
TypeMenu MenuType = 1
|
||||
TypeDragMenu MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra}
|
||||
TypeRadioMenu MenuType = 3 // ???
|
||||
TypeMainBackground MenuType = 45 // ???
|
||||
TypeDialogue MenuType = 300
|
||||
|
||||
TypeSimpleButton MenuType = 3
|
||||
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 // A "thumb" appears to be a vertical slider
|
||||
TypeLineBriefing MenuType = 41
|
||||
TypeInvokeButton MenuType = 50
|
||||
TypeDoorHotspot3 MenuType = 60 // Maybe? Appears in Arrange.mnu
|
||||
TypeOverlay MenuType = 61
|
||||
TypeHypertext MenuType = 70
|
||||
TypeCheckbox MenuType = 91
|
||||
TypeEditBox MenuType = 100
|
||||
TypeInventorySelect MenuType = 110
|
||||
TypeRadioButton MenuType = 120
|
||||
TypeDropdownButton MenuType = 200
|
||||
TypeComboBoxItem MenuType = 205
|
||||
TypeAnimationSample MenuType = 220
|
||||
TypeAnimationHover MenuType = 221 // FONTTYPE is animation speed. Only animate when hovered
|
||||
TypeMainButton MenuType = 228
|
||||
TypeSlider MenuType = 232
|
||||
TypeStatusBar MenuType = 233
|
||||
TypeDialogue MenuType = 300
|
||||
SubTypeSimpleButton SubMenuType = 3
|
||||
SubTypeDoorHotspot1 SubMenuType = 30 // Like a button I guess? "FONTTYPE is animation speed"
|
||||
SubTypeDoorHotspot2 SubMenuType = 31 // Seems like a duplicate of the above? What's different?
|
||||
SubTypeLineKbd SubMenuType = 40
|
||||
SubTypeLineBriefing SubMenuType = 41
|
||||
SubTypeThumb SubMenuType = 45 // A "thumb" appears to be a vertical slider
|
||||
SubTypeInvokeButton SubMenuType = 50
|
||||
SubTypeDoorHotspot3 SubMenuType = 60 // Maybe? Appears in Arrange.mnu
|
||||
SubTypeOverlay SubMenuType = 61
|
||||
SubTypeHypertext SubMenuType = 70
|
||||
SubTypeCheckbox SubMenuType = 91
|
||||
SubTypeEditBox SubMenuType = 100
|
||||
SubTypeInventorySelect SubMenuType = 110
|
||||
SubTypeRadioButton SubMenuType = 120
|
||||
SubTypeDropdownButton SubMenuType = 200
|
||||
SubTypeComboBoxItem SubMenuType = 205
|
||||
SubTypeAnimationSample SubMenuType = 220
|
||||
SubTypeAnimationHover SubMenuType = 221 // FONTTYPE is animation speed. Only animate when hovered
|
||||
SubTypeMainButton SubMenuType = 228
|
||||
SubTypeSlider SubMenuType = 232
|
||||
SubTypeStatusBar SubMenuType = 233
|
||||
|
||||
TypeListBoxUp MenuType = 400 // FIXME: these have multiple items in MENUTYPE
|
||||
TypeListBoxDown MenuType = 405
|
||||
SubTypeListBoxUp SubMenuType = 400 // FIXME: these have multiple items in SUBMENUTYPE
|
||||
SubTypeListBoxDown SubMenuType = 405
|
||||
)
|
||||
|
||||
// FIXME: certain elements - especially overlays - don't have a DESC specified
|
||||
@@ -64,46 +69,11 @@ var TextOverrides = map[string]string{
|
||||
"main:2.7": "0.1-ordoor",
|
||||
}
|
||||
|
||||
// FIXME: The menu is specified as type 2 (button) in these cases, which is
|
||||
// weird. Make it a menu for now.
|
||||
//
|
||||
// Hypothesis: MENUTYPE and SUBMENUTYPE are not equivalent?
|
||||
var TypeOverrides = map[string]MenuType{
|
||||
"levelply:2": TypeMenu,
|
||||
"savegame:2": TypeMenu,
|
||||
"loadgame:2": TypeMenu,
|
||||
|
||||
// "thumb" is not a background.
|
||||
"maingame:2": TypeStatic,
|
||||
|
||||
// ???
|
||||
"configure_ultequip:7.5": TypeListBoxUp,
|
||||
"configure_ultequip:7.6": TypeListBoxDown,
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Menu *Menu
|
||||
Parent *Record
|
||||
Children []*Record
|
||||
|
||||
Id int
|
||||
ObjectIdx int // Can be specified in MENUID, defaults to 0
|
||||
|
||||
Type MenuType
|
||||
DrawType int
|
||||
FontType int
|
||||
Active bool
|
||||
SpriteId []int
|
||||
Share int
|
||||
X int
|
||||
Y int
|
||||
|
||||
// From i18n
|
||||
Text string
|
||||
Help string
|
||||
|
||||
// FIXME: turn these into first-class data
|
||||
properties map[string]string
|
||||
var TypeOverrides = map[string]SubMenuType{
|
||||
// FIXME: These are put down as simple buttons, but it's a *lot* easier to
|
||||
// understand them as list box buttons.
|
||||
"configure_ultequip:7.5": SubTypeListBoxUp,
|
||||
"configure_ultequip:7.6": SubTypeListBoxDown,
|
||||
}
|
||||
|
||||
type Menu struct {
|
||||
@@ -120,7 +90,48 @@ type Menu struct {
|
||||
|
||||
// The actual menu records. There are multiple top-level items. Submenus are
|
||||
// only ever nested one deep.
|
||||
Groups []*Group
|
||||
}
|
||||
|
||||
// Group represents an element with a MENUTYPE. It is part of a Menu and may
|
||||
// have children.
|
||||
type Group struct {
|
||||
Menu *Menu
|
||||
Records []*Record
|
||||
|
||||
Properties
|
||||
Type MenuType
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Menu *Menu
|
||||
Group *Group
|
||||
|
||||
Properties
|
||||
Type SubMenuType
|
||||
}
|
||||
|
||||
type Properties struct {
|
||||
Locator string // Not strictly a property. Set for tracking.
|
||||
|
||||
ID int
|
||||
ObjectIdx int // Can be specified in MENUID, defaults to 0
|
||||
|
||||
Accelerator int
|
||||
Active bool
|
||||
Desc string
|
||||
DrawType int
|
||||
FontType int
|
||||
Moveable bool
|
||||
Share int
|
||||
SoundType int
|
||||
SpriteId []int
|
||||
X int
|
||||
Y int
|
||||
|
||||
// From i18n
|
||||
Text string
|
||||
Help string
|
||||
}
|
||||
|
||||
func LoadMenu(filename string) (*Menu, error) {
|
||||
@@ -218,7 +229,10 @@ func loadFonts(menu *Menu, scanner *asciiscan.Scanner) error {
|
||||
}
|
||||
|
||||
func loadRecords(baseDir string, menu *Menu, scanner *asciiscan.Scanner) error {
|
||||
var record *Record // We build records here and add them when complete
|
||||
// We build things up line by line in these variables
|
||||
var group *Group
|
||||
var record *Record
|
||||
var properties *Properties
|
||||
|
||||
for {
|
||||
str, err := scanner.ConsumeString()
|
||||
@@ -242,25 +256,49 @@ func loadRecords(baseDir string, menu *Menu, scanner *asciiscan.Scanner) error {
|
||||
continue
|
||||
}
|
||||
|
||||
switch str {
|
||||
case "*":
|
||||
if str == "*" {
|
||||
if record != nil {
|
||||
menu.Records = append(menu.Records, record.Toplevel())
|
||||
group.Records = append(group.Records, record)
|
||||
record = nil
|
||||
}
|
||||
|
||||
continue // NEXT RECORD
|
||||
case "~":
|
||||
return nil // THE END
|
||||
if group != nil {
|
||||
menu.Groups = append(menu.Groups, group)
|
||||
group = nil
|
||||
}
|
||||
|
||||
continue // New group
|
||||
}
|
||||
|
||||
if str == "~" {
|
||||
break // THE END
|
||||
}
|
||||
|
||||
k, v := asciiscan.ConsumeProperty(str)
|
||||
switch k {
|
||||
switch strings.ToUpper(k) {
|
||||
case "MENUID":
|
||||
record = newRecord(menu, nil)
|
||||
if group != nil {
|
||||
menu.Groups = append(menu.Groups, group)
|
||||
}
|
||||
|
||||
group = newGroup(menu, v)
|
||||
properties = &group.Properties
|
||||
case "SUBMENUID":
|
||||
record = newRecord(menu, record.Toplevel())
|
||||
if record != nil {
|
||||
group.Records = append(group.Records, record)
|
||||
}
|
||||
|
||||
record = newRecord(group, v)
|
||||
properties = &record.Properties
|
||||
case "MENUTYPE":
|
||||
group.setMenuType(v)
|
||||
case "SUBMENUTYPE":
|
||||
record.setSubMenuType(v)
|
||||
default:
|
||||
if err := properties.setProperty(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
setProperty(record, k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -295,85 +333,94 @@ func LoadMenus(dir string) (map[string]*Menu, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func newRecord(menu *Menu, parent *Record) *Record {
|
||||
out := &Record{
|
||||
Menu: menu,
|
||||
Parent: parent,
|
||||
properties: map[string]string{},
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
parent.Children = append(parent.Children, out)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Record) Toplevel() *Record {
|
||||
if r.Parent != nil {
|
||||
return r.Parent.Toplevel()
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func setProperty(r *Record, k, v string) {
|
||||
vSplit := strings.Split(v, ",")
|
||||
vInt, _ := strconv.Atoi(v)
|
||||
func listOfInts(s string) []int {
|
||||
vSplit := strings.Split(s, ",")
|
||||
vSplitInt := make([]int, len(vSplit))
|
||||
|
||||
for i, subV := range vSplit {
|
||||
vSplitInt[i], _ = strconv.Atoi(subV)
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "MENUID":
|
||||
// ObjectIdx can be specified in the MENUID. Only seen for .mni files
|
||||
if strings.Contains(v, ",") && len(vSplitInt) >= 2 {
|
||||
r.Id = vSplitInt[0]
|
||||
r.ObjectIdx = vSplitInt[1]
|
||||
} else {
|
||||
r.Id = vInt
|
||||
}
|
||||
case "SUBMENUID":
|
||||
if strings.Contains(v, ",") {
|
||||
log.Printf("%v has an object index in SUBMENUID - surprising", r.Locator())
|
||||
r.Id = vSplitInt[0]
|
||||
r.ObjectIdx = vSplitInt[1]
|
||||
} else {
|
||||
r.Id = vInt
|
||||
if r.Parent != nil { // Children seem to inherit from parents?
|
||||
r.ObjectIdx = r.Parent.ObjectIdx
|
||||
}
|
||||
}
|
||||
case "MENUTYPE", "SUBMENUTYPE":
|
||||
if strings.Contains(v, ",") {
|
||||
r.Type = MenuType(vSplitInt[0]) // FIXME: what are the other values in this case?
|
||||
} else {
|
||||
r.Type = MenuType(vInt)
|
||||
}
|
||||
return vSplitInt
|
||||
}
|
||||
|
||||
// FIXME: Type override. Note that MENUID is specified first, so this works
|
||||
if override, ok := TypeOverrides[r.Locator()]; ok {
|
||||
r.Type = override
|
||||
}
|
||||
case "ACTIVE":
|
||||
r.Active = (vInt != 0)
|
||||
case "SPRITEID":
|
||||
r.SpriteId = vSplitInt
|
||||
case "X-CORD":
|
||||
r.X = vInt
|
||||
case "Y-CORD":
|
||||
r.Y = vInt
|
||||
case "FONTTYPE":
|
||||
r.FontType = vInt
|
||||
case "DRAW TYPE":
|
||||
r.DrawType = vInt
|
||||
case "SHARE":
|
||||
r.Share = vInt
|
||||
default:
|
||||
r.properties[k] = v
|
||||
func newGroup(menu *Menu, idStr string) *Group {
|
||||
out := &Group{Menu: menu}
|
||||
|
||||
// ObjectIdx can be specified in the MENUID. Only seen for .mni files
|
||||
ints := listOfInts(idStr)
|
||||
out.ID = ints[0]
|
||||
if len(ints) > 1 {
|
||||
out.ObjectIdx = ints[1]
|
||||
}
|
||||
|
||||
out.Locator = fmt.Sprintf("%v:%v", menu.Name, out.ID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func newRecord(group *Group, idStr string) *Record {
|
||||
out := &Record{Group: group}
|
||||
|
||||
out.ID, _ = strconv.Atoi(idStr) // FIXME: we're ignoring conversion errors here
|
||||
out.ObjectIdx = group.ObjectIdx // FIXME: we shouldn't *copy* this
|
||||
|
||||
out.Locator = fmt.Sprintf("%v.%v", group.Locator, out.ID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (g *Group) setMenuType(s string) {
|
||||
v, _ := strconv.Atoi(s) // FIXME: conversion errors
|
||||
g.Type = MenuType(v)
|
||||
}
|
||||
|
||||
func (r *Record) setSubMenuType(s string) {
|
||||
// FIXME: Type overrides shouldn't be necessary!
|
||||
if override, ok := TypeOverrides[r.Locator]; ok {
|
||||
r.Type = override
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: what are the other types here? Related to list boxes?
|
||||
ints := listOfInts(s)
|
||||
r.Type = SubMenuType(ints[0])
|
||||
}
|
||||
|
||||
func (p *Properties) setProperty(k, v string) error {
|
||||
ints := listOfInts(v)
|
||||
vInt := ints[0]
|
||||
asBool := (vInt != 0)
|
||||
|
||||
switch strings.ToUpper(k) {
|
||||
case "ACCELERATOR":
|
||||
p.Accelerator = vInt
|
||||
case "ACTIVE":
|
||||
p.Active = asBool
|
||||
case "DESC":
|
||||
p.Desc = v // Usually int, occasionally string
|
||||
case "DRAW TYPE":
|
||||
p.DrawType = vInt
|
||||
case "FONTTYPE":
|
||||
p.FontType = vInt
|
||||
case "MOVEABLE":
|
||||
p.Moveable = asBool
|
||||
case "SOUNDTYPE":
|
||||
p.SoundType = vInt
|
||||
case "SPRITEID":
|
||||
p.SpriteId = ints
|
||||
case "X-CORD":
|
||||
p.X = vInt
|
||||
case "Y-CORD":
|
||||
p.Y = vInt
|
||||
case "SHARE":
|
||||
p.Share = vInt
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unknown property for %v: %v=%v", p.Locator, k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Replacer interface {
|
||||
@@ -382,43 +429,47 @@ type Replacer interface {
|
||||
}
|
||||
|
||||
func (r *Record) Internationalize(replacer Replacer) {
|
||||
if override, ok := TextOverrides[r.Locator()]; ok {
|
||||
delete(r.properties, "DESC")
|
||||
if override, ok := TextOverrides[r.Locator]; ok {
|
||||
r.Text = override
|
||||
return
|
||||
}
|
||||
|
||||
if override, ok := DescOverrides[r.Locator()]; ok {
|
||||
r.properties["DESC"] = strconv.Itoa(override)
|
||||
if override, ok := DescOverrides[r.Locator]; ok {
|
||||
r.Desc = strconv.Itoa(override)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(r.properties["DESC"])
|
||||
id, err := strconv.Atoi(r.Desc)
|
||||
if err == nil {
|
||||
delete(r.properties, "DESC")
|
||||
replacer.ReplaceText(id, &r.Text)
|
||||
replacer.ReplaceHelp(id, &r.Help)
|
||||
}
|
||||
|
||||
for _, child := range r.Children {
|
||||
child.Internationalize(replacer)
|
||||
} else {
|
||||
r.Text = r.Desc // Sometimes it's a string like "EQUIPMENT"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) Internationalize(replacer Replacer) {
|
||||
for _, record := range m.Records {
|
||||
record.Internationalize(replacer)
|
||||
for _, group := range m.Groups {
|
||||
for _, record := range group.Records {
|
||||
record.Internationalize(replacer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Record) Path() string {
|
||||
var path []string
|
||||
func (g *Group) Props() *Properties {
|
||||
return &g.Properties
|
||||
}
|
||||
|
||||
for rec := r; rec != nil; rec = rec.Parent {
|
||||
path = append([]string{strconv.Itoa(rec.Id)}, path...)
|
||||
func (r *Record) Props() *Properties {
|
||||
return &r.Properties
|
||||
}
|
||||
|
||||
func (p *Properties) BaseSpriteID() int {
|
||||
base := p.Share
|
||||
|
||||
// SpriteId takes precedence if present
|
||||
if len(p.SpriteId) > 0 && p.SpriteId[0] >= 0 {
|
||||
base = p.SpriteId[0]
|
||||
}
|
||||
|
||||
return strings.Join(path, ".")
|
||||
}
|
||||
|
||||
func (r *Record) Locator() string {
|
||||
return fmt.Sprintf("%v:%v", r.Menu.Name, r.Path())
|
||||
return base
|
||||
}
|
||||
|
Reference in New Issue
Block a user