2018-12-30 23:23:08 +00:00
|
|
|
package menus
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2019-12-31 01:55:58 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
2018-12-30 23:23:08 +00:00
|
|
|
)
|
|
|
|
|
2020-03-22 22:12:59 +00:00
|
|
|
type MenuType int
|
|
|
|
|
2020-03-21 18:50:26 +00:00
|
|
|
const (
|
2020-03-22 22:12:59 +00:00
|
|
|
TypeStatic MenuType = 0
|
|
|
|
TypeMenu MenuType = 1
|
|
|
|
TypeButton MenuType = 3
|
|
|
|
TypeInvokeButton MenuType = 50
|
|
|
|
TypeOverlay MenuType = 61
|
|
|
|
TypeHypertext MenuType = 70
|
|
|
|
TypeCheckbox MenuType = 91
|
|
|
|
TypeAnimationSample MenuType = 220
|
|
|
|
TypeMainButton MenuType = 228
|
|
|
|
TypeSlider MenuType = 232
|
2020-03-21 18:50:26 +00:00
|
|
|
)
|
|
|
|
|
2018-12-30 23:23:08 +00:00
|
|
|
type Record struct {
|
|
|
|
Parent *Record
|
|
|
|
Children []*Record
|
|
|
|
|
|
|
|
Id int
|
2020-03-22 22:12:59 +00:00
|
|
|
Type MenuType
|
2020-03-21 18:50:26 +00:00
|
|
|
DrawType int
|
2019-10-09 00:41:41 +01:00
|
|
|
FontType int
|
2018-12-30 23:23:08 +00:00
|
|
|
Active bool
|
2019-10-09 00:41:41 +01:00
|
|
|
SpriteId []int
|
2020-03-21 18:50:26 +00:00
|
|
|
Share int
|
2018-12-30 23:23:08 +00:00
|
|
|
X int
|
|
|
|
Y int
|
2019-01-02 06:16:15 +00:00
|
|
|
Desc string
|
2018-12-30 23:23:08 +00:00
|
|
|
|
|
|
|
// FIXME: turn these into first-class data
|
|
|
|
properties map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Menu struct {
|
|
|
|
Name string
|
|
|
|
// TODO: load these
|
|
|
|
ObjectFiles []string
|
|
|
|
FontNames []string
|
|
|
|
|
|
|
|
// FIXME: turn these into first-class data
|
|
|
|
Properties map[string]string
|
|
|
|
|
|
|
|
// The actual menu records. There are multiple top-level items. Submenus are
|
|
|
|
// only ever nested one deep.
|
|
|
|
Records []*Record
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoadMenu(filename string) (*Menu, error) {
|
|
|
|
name := filepath.Base(filename)
|
|
|
|
|
|
|
|
// FIXME: this needs turning into a real parser sometime
|
|
|
|
scanner, err := asciiscan.New(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-01-02 04:38:03 +00:00
|
|
|
defer scanner.Close()
|
|
|
|
|
2018-12-30 23:23:08 +00:00
|
|
|
var str string
|
|
|
|
var record *Record
|
|
|
|
|
|
|
|
section := 0
|
|
|
|
isProp := false
|
|
|
|
out := &Menu{
|
|
|
|
Name: name,
|
|
|
|
Properties: map[string]string{},
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
str, err = scanner.ConsumeString()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Whether the lines are properties or not alternate with each section,
|
|
|
|
// except the records use `*` as a separator
|
|
|
|
if section < 3 && isProp != asciiscan.IsProperty(str) {
|
|
|
|
section += 1
|
|
|
|
isProp = !isProp
|
|
|
|
}
|
|
|
|
|
|
|
|
if str == "~" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
switch section {
|
|
|
|
case 0: // List of object files
|
|
|
|
out.ObjectFiles = append(out.ObjectFiles, str)
|
|
|
|
case 1: // List of properties
|
|
|
|
k, v := asciiscan.ConsumeProperty(str)
|
|
|
|
out.Properties[k] = v
|
|
|
|
case 2: // list of fonts
|
|
|
|
// FIXME: do we need to do something cleverer here?
|
|
|
|
if str == "NULL" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
out.FontNames = append(out.FontNames, str)
|
|
|
|
case 3: // Menu records
|
|
|
|
if str == "*" { // NEXT RECORD
|
|
|
|
out.Records = append(out.Records, record.Toplevel())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
k, v := asciiscan.ConsumeProperty(str)
|
|
|
|
switch k {
|
|
|
|
case "MENUID":
|
|
|
|
record = newRecord(nil)
|
|
|
|
case "SUBMENUID":
|
|
|
|
record = newRecord(record.Toplevel())
|
|
|
|
}
|
|
|
|
setProperty(record, k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoadMenus(dir string) (map[string]*Menu, error) {
|
|
|
|
fis, err := ioutil.ReadDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
out := make(map[string]*Menu, len(fis))
|
|
|
|
|
|
|
|
for _, fi := range fis {
|
|
|
|
relname := fi.Name()
|
|
|
|
basename := filepath.Base(relname)
|
|
|
|
extname := filepath.Ext(relname)
|
|
|
|
|
|
|
|
// Skip anything that isn't a .mnu file
|
|
|
|
if !strings.EqualFold(extname, ".mnu") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
built, err := LoadMenu(filepath.Join(dir, relname))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%s: %v", filepath.Join(dir, relname), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
out[basename] = built
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRecord(parent *Record) *Record {
|
|
|
|
out := &Record{
|
|
|
|
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) {
|
2019-10-09 00:41:41 +01:00
|
|
|
vSplit := strings.Split(v, ",")
|
2018-12-30 23:23:08 +00:00
|
|
|
vInt, _ := strconv.Atoi(v)
|
2019-10-09 00:41:41 +01:00
|
|
|
vSplitInt := make([]int, len(vSplit))
|
|
|
|
|
|
|
|
for i, subV := range vSplit {
|
|
|
|
vSplitInt[i], _ = strconv.Atoi(subV)
|
|
|
|
}
|
|
|
|
|
2018-12-30 23:23:08 +00:00
|
|
|
switch k {
|
|
|
|
case "MENUID", "SUBMENUID":
|
|
|
|
r.Id = vInt
|
|
|
|
case "MENUTYPE", "SUBMENUTYPE":
|
2020-03-22 22:12:59 +00:00
|
|
|
r.Type = MenuType(vInt)
|
2018-12-30 23:23:08 +00:00
|
|
|
case "ACTIVE":
|
|
|
|
r.Active = (vInt != 0)
|
|
|
|
case "SPRITEID":
|
2019-10-09 00:41:41 +01:00
|
|
|
r.SpriteId = vSplitInt
|
2018-12-30 23:23:08 +00:00
|
|
|
case "X-CORD":
|
|
|
|
r.X = vInt
|
|
|
|
case "Y-CORD":
|
|
|
|
r.Y = vInt
|
2019-01-02 06:16:15 +00:00
|
|
|
case "DESC":
|
|
|
|
r.Desc = v
|
2019-10-09 00:41:41 +01:00
|
|
|
case "FONTTYPE":
|
|
|
|
r.FontType = vInt
|
2020-03-21 18:50:26 +00:00
|
|
|
case "DRAW TYPE":
|
|
|
|
r.DrawType = vInt
|
|
|
|
case "SHARE":
|
|
|
|
r.Share = vInt
|
2018-12-30 23:23:08 +00:00
|
|
|
default:
|
|
|
|
r.properties[k] = v
|
|
|
|
}
|
|
|
|
}
|
2019-01-02 06:16:15 +00:00
|
|
|
|
|
|
|
type Replacer interface {
|
|
|
|
Replace(int, *string)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Record) Internationalize(replacer Replacer) {
|
|
|
|
id, err := strconv.Atoi(r.Desc)
|
|
|
|
if err == nil {
|
|
|
|
replacer.Replace(id, &r.Desc)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, child := range r.Children {
|
|
|
|
child.Internationalize(replacer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) Internationalize(replacer Replacer) {
|
|
|
|
for _, record := range m.Records {
|
|
|
|
record.Internationalize(replacer)
|
|
|
|
}
|
|
|
|
}
|