package main import ( "flag" "image" "log" "os" "path/filepath" "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/menus" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") menuFile = flag.String("menu", "", "Name of a menu, e.g. Main") ) type env struct { menu *menus.Menu objects []*assetstore.Object // fonts []*assetstore.Font // fontObjs []*assetstore.Object step int state state lastState state } type state struct { // Redraw the window if these change winBounds image.Rectangle } func main() { flag.Parse() if *gamePath == "" || *menuFile == "" { flag.Usage() os.Exit(1) } assets, err := assetstore.New(*gamePath) if err != nil { log.Fatal(err) } menu, err := menus.LoadMenu(*menuFile) if err != nil { log.Fatalf("Couldn't load menu file %s: %v", *menuFile, err) } if i18n, err := data.LoadI18n(filepath.Join(*gamePath, "Data", data.I18nFile)); err != nil { log.Printf("Failed to load i18n data, skipping internationalization: %v", err) } else { menu.Internationalize(i18n) } // loadedFonts, err := loadFonts(menu.FontNames...) // if err != nil { // log.Fatalf("Failed to load font: %v", err) // } var menuObjs []*assetstore.Object for _, filename := range menu.ObjectFiles { obj, err := assets.ObjectByPath(filepath.Join(*gamePath, "Menu", filename)) if err != nil { log.Fatalf("Failed to load %v: %v", filename, err) } menuObjs = append(menuObjs, obj) } state := state{} env := &env{ menu: menu, objects: menuObjs, // fonts: loadedFonts, state: state, lastState: state, } win, err := ui.NewWindow("View Menu: " + *menuFile) if err != nil { log.Fatal("Couldn't create window: %v", err) } if err := win.Run(env.Update, env.Draw); err != nil { log.Fatal(err) } } func (e *env) Update() error { // No behaviour yet e.step += 1 e.lastState = e.state return nil } const ( origX = 640.0 origY = 480.0 ) func (e *env) Draw(screen *ebiten.Image) error { // The menus expect to be drawn to a 640x480 screen. We need to scale and // project that so it fills the window appropriately. This is a combination // of translate + zoom winSize := screen.Bounds().Max scaleX := float64(winSize.X) / float64(origX) scaleY := float64(winSize.Y) / float64(origY) cam := ebiten.GeoM{} cam.Scale(scaleX, scaleY) for _, record := range e.menu.Records { if err := e.drawRecordRecursive(record, screen, cam); err != nil { return err } } return nil } func (e *env) drawRecordRecursive(record *menus.Record, screen *ebiten.Image, geo ebiten.GeoM) error { if err := e.drawRecord(record, screen, geo); err != nil { return err } // Draw all children of this record for _, child := range record.Children { if err := e.drawRecordRecursive(child, screen, geo); err != nil { return err } } return nil } // If the record has a "share" type, we can work out whether it's func (e *env) isFocused(record *menus.Record, geo ebiten.GeoM) bool { if record.Share < 0 { return false } sprite, err := e.objects[0].Sprite(record.Share) // FIXME: need to handle multiple objects if err != nil { return false } invGeo := geo invGeo.Invert() cX, cY := ebiten.CursorPosition() cursorX, cursorY := invGeo.Apply(float64(cX), float64(cY)) // Undo screen scaling cursorPoint := image.Pt(int(cursorX), int(cursorY)) return cursorPoint.In(sprite.Rect) } func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, geo ebiten.GeoM) error { // Draw this record if it's valid to do so. FIXME: lots to learn spriteId := record.SelectSprite( e.step/2, ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft), e.isFocused(record, geo), ) if spriteId < 0 { return nil } // X-CORD and Y-CORD are universally either 0 or -1, so ignore here. // TODO: maybe 0 overrides in-sprite offset (set below)? // FIXME: Need to handle multiple objects obj := e.objects[0] sprite, err := obj.Sprite(spriteId) if err != nil { return err } // Account for scaling, draw sprite at its specified offset x, y := geo.Apply(float64(sprite.XOffset), float64(sprite.YOffset)) // log.Printf( // "Drawing id=%v type=%v spriteid=%v x=%v(+%v) y=%v(%+v) desc=%q parent=%p", // record.Id, record.Type, spriteId, record.X, record.Y, sprite.XOffset, sprite.YOffset, record.Desc, record.Parent, // ) geo.Translate(x, y) screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: geo}) // FIXME: we probably shouldn't draw everything? // FIXME: handle multiple fonts // if len(e.fonts) > 0 && record.Desc != "" { // e.fonts[0].Output(screen, origOffset, record.Desc) // } return nil }