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:
2020-04-14 03:14:49 +01:00
parent dc131939f4
commit 786d261f98
18 changed files with 1034 additions and 847 deletions

View File

@@ -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
}