package main import ( "flag" "fmt" "log" "os" "path/filepath" "github.com/faiface/pixel" "github.com/faiface/pixel/pixelgl" "golang.org/x/image/colornames" "ur.gs/ordoor/internal/conv" "ur.gs/ordoor/internal/data" "ur.gs/ordoor/internal/fonts" "ur.gs/ordoor/internal/menus" "ur.gs/ordoor/internal/ui" ) var ( gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") menuFile = flag.String("menu", "", "Path to a .mnu file, e.g. ./orig/Menu/MainGame.mnu") ) type env struct { menu *menus.Menu objects []*conv.Object batch *pixel.Batch fonts []*conv.Font fontObjs []*conv.Object fontBatch *pixel.Batch } type state struct { env *env cam pixel.Matrix step int // Redraw the window if these change winPos pixel.Vec winBounds pixel.Rect } func loadObjects(names ...string) ([]*conv.Object, *pixel.Batch) { var raw []*data.Object for _, name := range names { objFile := filepath.Join(filepath.Dir(*menuFile), name) obj, err := data.LoadObject(objFile) if err != nil { log.Fatalf("Failed to load %s: %v", name, err) } obj.Name = name raw = append(raw, obj) } objects, spritesheet := conv.ConvertObjects(raw) batch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) return objects, batch } func main() { flag.Parse() if *gamePath == "" || *menuFile == "" { flag.Usage() os.Exit(1) } 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) } menuObjs, menuBatch := loadObjects(menu.ObjectFiles...) env := &env{ menu: menu, objects: menuObjs, batch: menuBatch, fonts: loadedFonts, } // The main thread now belongs to pixelgl pixelgl.Run(env.run) } func loadFonts(names ...string) ([]*conv.Font, error) { var out []*conv.Font for _, name := range names { fnt, err := fonts.LoadFont(filepath.Join(*gamePath, "Fonts", name+".fnt")) if err != nil { return nil, fmt.Errorf("%v: %v", name, err) } out = append(out, conv.ConvertFont(fnt)) } return out, nil } func (e *env) run() { win, err := ui.NewWindow("View Menu: " + *menuFile) if err != nil { log.Fatal("Couldn't create window: %v", err) } pWin := win.PixelWindow state := &state{env: e} // For now, just try to display the various objects // left + right to change object, up + down to change frame win.Run(func() { oldState := *state state = state.runStep(pWin) if oldState != *state || oldState.step == 0 { state.present(pWin) } state.step += 1 }) } func (s *state) runStep(pWin *pixelgl.Window) *state { newState := *s newState.winPos = pWin.GetPos() newState.winBounds = pWin.Bounds() newState.handleKeys(pWin) return &newState } const ( origX = 640.0 origY = 480.0 ) func (s *state) present(pWin *pixelgl.Window) { pWin.Clear(colornames.Black) s.env.batch.Clear() // 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 := pWin.Bounds().Max scaleFactor := pixel.Vec{winSize.X / origX, winSize.Y / origY} cam := pixel.IM cam = cam.ScaledXY(pixel.ZV, pixel.Vec{1.0, -1.0}) // invert the Y axis cam = cam.Moved(pixel.Vec{origX / 2, origY / 2}) cam = cam.ScaledXY(pixel.ZV, scaleFactor) s.cam = cam s.env.batch.SetMatrix(cam) textCanvas := pixelgl.NewCanvas(pWin.Bounds()) textCanvas.SetMatrix(pixel.IM.ScaledXY(pixel.ZV, scaleFactor)) for _, record := range s.env.menu.Records { s.drawRecord(record, s.env.batch, textCanvas) } s.env.batch.Draw(pWin) textCanvas.Draw(pWin, pixel.IM) } func (s *state) drawRecord(record *menus.Record, target, textTarget pixel.Target) { // Draw this record if it's valid to do so. FIXME: lots to learn if len(record.SpriteId) >= 0 { spriteId := record.SpriteId[0] x := float64(record.X) y := float64(record.Y) // Theory: we either give spriteid, or y,x,spriteId if len(record.SpriteId) == 3 { x = x + float64(record.SpriteId[1]) y = y + float64(record.SpriteId[0]*2) // FIXME: *2 works, no idea spriteId = record.SpriteId[2] } // FIXME: some here are set at -1. Presume that means don't draw. if spriteId < 0 { goto out } // FIXME: some are set at -1, -1. No idea why. Origin? if x < 0.0 { x = 0.0 } if y < 0.0 { y = 0.0 } log.Printf( "Drawing id=%v type=%v spriteid=%v x=%v y=%v desc=%q parent=%p", record.Id, record.Type, spriteId, record.X, record.Y, record.Desc, record.Parent, ) // FIXME: Need to handle multiple objects offset := pixel.V(x, y) obj := s.env.objects[0] sprite := obj.Sprites[spriteId] sprite.Spr.Draw(target, pixel.IM.Moved(offset)) // FIXME: we probably shouldn't draw everything? // FIXME: handle multiple fonts if len(s.env.fonts) > 0 && record.Desc != "" { s.env.fonts[0].Output(textTarget, pixel.IM.Moved(offset), record.Desc) } } out: // Draw all children of this record for _, child := range record.Children { s.drawRecord(child, target, textTarget) } } func (s *state) handleKeys(pWin *pixelgl.Window) { if pWin.JustPressed(pixelgl.MouseButton1) { log.Printf("cam: %#v", s.cam) pos := s.cam.Unproject(pWin.MousePosition()) log.Printf("X=%v Y=%v", pos.X, pos.Y) } /* if pWin.JustPressed(pixelgl.KeyLeft) { if s.objIdx > 0 { s.objIdx -= 1 s.spriteIdx = 0 } } if pWin.JustPressed(pixelgl.KeyRight) { if s.objIdx < s.env.set.Count()-1 { s.objIdx += 1 s.spriteIdx = 0 } } if pWin.JustPressed(pixelgl.KeyDown) { if s.spriteIdx > 0 { s.spriteIdx -= 1 } } if pWin.JustPressed(pixelgl.KeyUp) { if s.spriteIdx < len(s.curObject().Sprites)-1 { s.spriteIdx += 1 } } // Zoom in and out with the mouse wheel s.zoom *= math.Pow(1.2, pWin.MouseScroll().Y) */ }