package main import ( "bytes" "flag" "image/color" "log" "math" "os" "path/filepath" "github.com/faiface/pixel" // "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/pixelgl" "golang.org/x/image/colornames" "ur.gs/chaos-gate/internal/data" "ur.gs/chaos-gate/internal/sets" "ur.gs/chaos-gate/internal/ui" ) var ( gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") setFile = flag.String("set", "", "Path to a .set file, e.g. ./orig/Sets/map01.set") ) type env struct { set *sets.MapSet objects map[string]*data.Object } type state struct { env *env step int objIdx int spriteIdx int zoom float64 cam pixel.Matrix camPos pixel.Vec } func main() { flag.Parse() if *gamePath == "" || *setFile == "" { flag.Usage() os.Exit(1) } mapSet, err := sets.LoadSet(*setFile) if err != nil { log.Fatalf("Couldn't load set file %s: %v", setFile, err) } objects := make(map[string]*data.Object) for _, name := range mapSet.Palette { objFile := filepath.Join(*gamePath, "Obj", name+".obj") obj, err := data.LoadObject(objFile) if err != nil { log.Fatalf("Failed to load %s: %v", name, err) } objects[filepath.Base(objFile)] = obj } env := &env{objects: objects, set: mapSet} // The main thread now belongs to pixelgl pixelgl.Run(env.run) } func (e *env) run() { win, err := ui.NewWindow("View Set: " + *setFile) if err != nil { log.Fatal("Couldn't create window: %v", err) } pWin := win.PixelWindow state := &state{ env: e, camPos: pixel.V(0, float64(-pWin.Bounds().Size().Y)), zoom: 8.0, } // 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 { log.Printf( "new state: numObj=%d object=%d (%s) numFrames=%d sprite=%d zoom=%.2f", state.env.set.Count(), state.objIdx, state.env.set.Palette[state.objIdx], // FIXME: palette is a confusing name state.curObject().NumSprites, state.spriteIdx, state.zoom, ) state.step += 1 state.present(pWin) } }) } func (s *state) runStep(pWin *pixelgl.Window) *state { newState := *s newState.handleKeys(pWin) return &newState } // WIP. Try to convert the pixeldata into a picture. func spriteToPic(sprite *data.Sprite) *pixel.PictureData { pic := pixel.MakePictureData(pixel.R(float64(0), float64(0), float64(sprite.Width), float64(sprite.Height))) buf := bytes.NewBuffer(sprite.PixelData) // The pixeldata seems to be formed of Y null-terminated records, with // varying numbers of bytes in each row. Probably [type, *data] but ignore // type for now. // // Theory: perhaps the data in each X is centered around the origin? for y := 0; y < int(sprite.Height); y++ { rowData, err := buf.ReadBytes(0) if err != nil { log.Printf("Error at y=%d: %v", y, err) continue } leftPad := (int(sprite.Width) - len(rowData)) / 2 for x, b := range rowData { idx := pic.Index(pixel.V(float64(leftPad+x), float64(y))) pic.Pix[idx] = color.RGBA{ R: b, G: b, B: b, A: 255, } } } return pic } func (s *state) present(pWin *pixelgl.Window) { obj := s.curObject() sprite := obj.Sprites[s.spriteIdx] pic := spriteToPic(sprite) center := pWin.Bounds().Center() cam := pixel.IM cam = cam.ScaledXY(center, pixel.Vec{1.0, -1.0}) // invert the Y axis cam = cam.Scaled(center, s.zoom) // apply current zoom factor //cam = cam.Moved(center.Sub(s.camPos)) // Make it central //cam = cam.Rotated(center, -0.785) // Apply isometric angle s.cam = cam pWin.SetMatrix(s.cam) pWin.Clear(colornames.Black) pixel.NewSprite(pic, pic.Bounds()).Draw(pWin, pixel.IM.Moved(center)) } func (s *state) handleKeys(pWin *pixelgl.Window) { 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 < int(s.curObject().NumSprites)-1 { s.spriteIdx += 1 } } // Zoom in and out with the mouse wheel s.zoom *= math.Pow(1.2, pWin.MouseScroll().Y) } func (s *state) curObject() *data.Object { name := s.env.set.Palette[s.objIdx] + ".obj" return s.env.objects[name] // FIXME: we should use consistent naming! }