179 lines
3.3 KiB
Go
179 lines
3.3 KiB
Go
|
package menus
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"ur.gs/ordoor/internal/util/asciiscan"
|
||
|
)
|
||
|
|
||
|
type Record struct {
|
||
|
Parent *Record
|
||
|
Children []*Record
|
||
|
|
||
|
Id int
|
||
|
Type int
|
||
|
Active bool
|
||
|
SpriteId int
|
||
|
X int
|
||
|
Y int
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
vInt, _ := strconv.Atoi(v)
|
||
|
switch k {
|
||
|
case "MENUID", "SUBMENUID":
|
||
|
r.Id = vInt
|
||
|
case "MENUTYPE", "SUBMENUTYPE":
|
||
|
r.Type = vInt
|
||
|
case "ACTIVE":
|
||
|
r.Active = (vInt != 0)
|
||
|
case "SPRITEID":
|
||
|
r.SpriteId = vInt
|
||
|
case "X-CORD":
|
||
|
r.X = vInt
|
||
|
case "Y-CORD":
|
||
|
r.Y = vInt
|
||
|
default:
|
||
|
r.properties[k] = v
|
||
|
}
|
||
|
}
|