package main import ( "flag" "log" "math" "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/maps" "ur.gs/ordoor/internal/sets" "ur.gs/ordoor/internal/ui" ) var ( gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") mapFile = flag.String("map", "", "Prefix path to a .map file, e.g. ./orig/Maps/Chapter01.MAP") txtFile = flag.String("txt", "", "Prefix path to a .txt file, e.g. ./orig/Maps/Chapter01.txt") ) type env struct { gameMap *maps.GameMap set *sets.MapSet objects map[string]*conv.Object sprites map[string][]*pixel.Sprite } type state struct { env *env step int cam pixel.Matrix camPos pixel.Vec zoom float64 rot float64 zIdx int } func main() { flag.Parse() if *gamePath == "" || *mapFile == "" || *txtFile == "" { flag.Usage() os.Exit(1) } gameMap, err := maps.LoadGameMapByFiles(*mapFile, *txtFile) if err != nil { log.Fatalf("Couldn't load map file: %v", err) } setFile := filepath.Join(*gamePath, "Sets", gameMap.MapSetFilename()) log.Println(setFile) mapSet, err := sets.LoadSet(setFile) if err != nil { log.Fatalf("Couldn't load set file %s: %v", setFile, err) } objects := make(map[string]*conv.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[name] = conv.ConvertObject(obj, name) } env := &env{gameMap: gameMap, set: mapSet, objects: objects} // The main thread now belongs to pixelgl pixelgl.Run(env.run) } func (e *env) run() { win, err := ui.NewWindow("View Map " + *mapFile) 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)), // camPos: pixel.V(float64(3700), float64(0)), zoom: 1.0, rot: 0.785, } pWin.SetSmooth(true) win.Run(func() { oldState := *state state = state.runStep(pWin) if oldState != *state || oldState.step == 0 { log.Printf("zoom=%.2f rot=%.4f zIdx=%v camPos=%#v", state.zoom, state.rot, state.zIdx, state.camPos) state.present(pWin) } state.step += 1 }) } func (e *env) getSprite(palette []string, ref maps.ObjRef) (*conv.Sprite, *conv.Object) { if ref.Index() >= len(palette) { log.Printf("Palette too small: %v requested", ref.Index()) return nil, nil } name := palette[ref.Index()] obj := e.objects[name] if obj == nil { log.Printf("Failed to find surface sprite %#v -> %q", ref, name) return nil, nil } if ref.Frame() >= len(obj.Sprites) { log.Printf("Out-of-index sprite %v requested for %v", ref.Frame(), name) return nil, obj } return &obj.Sprites[ref.Frame()], obj } var ( cellWidth = 64.0 // I think, anyway cellLength = 64.0 ) // TODO: build all the sprites in the set into a single spritesheet so we can // use pixel.Batch func (s *state) present(pWin *pixelgl.Window) { gameMap := s.env.gameMap pWin.Clear(colornames.Black) center := pWin.Bounds().Center() cam := pixel.IM cam = cam.ScaledXY(center, pixel.Vec{1.0, -1.0}) // invert the Y axis cam = cam.Scaled(pixel.ZV, s.zoom) // apply current zoom factor cam = cam.Moved(center.Sub(s.camPos)) // Make it central // cam = cam.Rotated(center.Sub(s.camPos), s.rot) // Apply isometric angle s.cam = cam pWin.SetMatrix(cam) // TODO: bounds clipping z := int(s.zIdx) for y := int(gameMap.MinLength); y < int(gameMap.MaxLength); y++ { for x := int(gameMap.MinWidth); x < int(gameMap.MaxWidth); x++ { cell := gameMap.Cells.At(x, y, z) surfaceSprite, _ := s.env.getSprite(s.env.set.SurfacePalette, cell.Surface) centerSprite, _ := s.env.getSprite(s.env.set.CenterPalette, cell.Center) fX := float64(x) fY := float64(y) xPos := fX * cellWidth yPos := fY * cellLength // The rotation translates the rectangular coordinates to diamond // ones \o/ // FIXME: these are off by a bit orig := pixel.V(xPos, yPos) iso := pixel.V(orig.X-orig.Y, (orig.X+orig.Y)/2.0) if surfaceSprite != nil { surfaceSprite.Spr.Draw(pWin, pixel.IM.Moved(iso)) } if centerSprite != nil { centerSprite.Spr.Draw(pWin, pixel.IM.Moved(iso)) } } } } func (s *state) runStep(pWin *pixelgl.Window) *state { newState := *s newState.handleKeys(pWin) return &newState } func (s *state) handleKeys(pWin *pixelgl.Window) { if pWin.Pressed(pixelgl.KeyLeft) { s.camPos.X -= 64 } if pWin.Pressed(pixelgl.KeyRight) { s.camPos.X += 64 } if pWin.Pressed(pixelgl.KeyDown) { s.camPos.Y -= 64 } if pWin.Pressed(pixelgl.KeyUp) { s.camPos.Y += 64 } for i := 1; i <= 7; i++ { if pWin.JustPressed(pixelgl.Key0 + pixelgl.Button(i)) { s.zIdx = i - 1 } } if pWin.Pressed(pixelgl.KeyMinus) { s.rot -= 0.001 } if pWin.Pressed(pixelgl.KeyEqual) { s.rot += 0.001 } // Zoom in and out with the mouse wheel s.zoom *= math.Pow(1.2, pWin.MouseScroll().Y) }