package menus import ( "fmt" "io/ioutil" "path/filepath" "strconv" "strings" "code.ur.gs/lupine/ordoor/internal/util/asciiscan" ) type MenuType int const ( 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 ) type Record struct { Parent *Record Children []*Record Id int Type MenuType DrawType int FontType int Active bool SpriteId []int Share int X int Y int Desc string // 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 } defer scanner.Close() 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) { vSplit := strings.Split(v, ",") vInt, _ := strconv.Atoi(v) vSplitInt := make([]int, len(vSplit)) for i, subV := range vSplit { vSplitInt[i], _ = strconv.Atoi(subV) } switch k { case "MENUID", "SUBMENUID": r.Id = vInt case "MENUTYPE", "SUBMENUTYPE": r.Type = MenuType(vInt) case "ACTIVE": r.Active = (vInt != 0) case "SPRITEID": r.SpriteId = vSplitInt case "X-CORD": r.X = vInt case "Y-CORD": r.Y = vInt case "DESC": r.Desc = v case "FONTTYPE": r.FontType = vInt case "DRAW TYPE": r.DrawType = vInt case "SHARE": r.Share = vInt default: r.properties[k] = v } } 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) } } func (r *Record) Path() string { var path []string for rec := r; rec != nil; rec = rec.Parent { path = append([]string{strconv.Itoa(rec.Id)}, path...) } return strings.Join(path, ".") }