package main import ( "flag" "log" "math" "os" "time" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/pixelgl" "golang.org/x/image/colornames" "ur.gs/chaos-gate/internal/maps" ) 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") winX = flag.Int("win-x", 1280, "width of the view-map window") winY = flag.Int("win-y", 1024, "height of the view-map window") ) 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) } // The main thread now belongs to pixelgl pixelgl.Run(func() { run(gameMap) }) } type runState struct { redraw bool started time.Time autoUpdate bool gameMap *maps.GameMap zoom float64 zIdx int cellIdx int } func run(gameMap *maps.GameMap) { cfg := pixelgl.WindowConfig{ Title: "View Map " + *mapFile, Bounds: pixel.R(0, 0, float64(*winX), float64(*winY)), VSync: true, } win, err := pixelgl.NewWindow(cfg) if err != nil { log.Fatal("Couldn't create window: %v", err) } state := &runState{ redraw: true, started: time.Now(), autoUpdate: true, gameMap: gameMap, zoom: 1.0, } for !win.Closed() { if state.redraw { log.Printf("z=%d cellIdx=%d", state.zIdx, state.cellIdx) presentFull(win, state) } state = runStep(win, state) win.Update() } } // TODO: cut this down to showing just the viewport? // The naive approach using gameMap.Width() / Height() cuts half the map off :/ func presentFull(win *pixelgl.Window, state *runState) { gameMap := state.gameMap center := win.Bounds().Center() sz := win.Bounds().Size() imd := imdraw.New(nil) // Rotate everything 45' anticlockwise to get an isometric view with the // lowest coordinates at the (now) top corner imd.SetMatrix(pixel.IM.Rotated(center, -math.Pi/4)) xPerCell := sz.X / float64(maps.MaxWidth) yPerCell := sz.Y / float64(maps.MaxLength) for y := 0; y < maps.MaxLength; y++ { for x := 0; x < maps.MaxWidth; x++ { min := pixel.Vec{X: float64(x) * xPerCell, Y: float64(y) * yPerCell} max := pixel.Vec{X: min.X + xPerCell, Y: min.Y + yPerCell} cell := gameMap.Cells.At(int(x), int(y), int(state.zIdx)) // TODO: represent the state of the cell *sensibly*, using colour. // Need to understand the contents better first, so for now optimize // for exploration // // CellIndex=3 shows walls & elevation changes imd.Color = makeColour(cell, state.cellIdx) imd.Push(min, max) imd.Rectangle(0.0) } } cam := pixel.IM.Scaled(center, state.zoom) win.SetMatrix(cam) win.Clear(colornames.Black) imd.Draw(win) } func makeColour(cell maps.Cell, colIdx int) pixel.RGBA { return pixel.RGB( float64(cell[colIdx]), float64(cell[colIdx]), float64(cell[colIdx]), ) } func runStep(win *pixelgl.Window, state *runState) *runState { nextState := *state nextState.redraw = false // Enable / disable auto-update with the enter key if win.JustPressed(pixelgl.KeyEnter) { nextState.autoUpdate = !state.autoUpdate log.Printf("autoUpdate=%v", nextState.autoUpdate) if nextState.autoUpdate { nextState.started = time.Now() } } // Automatically cycle every second when auto-update is on if nextState.autoUpdate && time.Now().Sub(nextState.started) > time.Second { nextState.redraw = true nextState.cellIdx = nextState.cellIdx + 1 if nextState.cellIdx >= maps.CellSize { nextState.cellIdx = 0 nextState.zIdx = nextState.zIdx + 1 } if nextState.zIdx >= maps.MaxHeight { nextState.zIdx = 0 } nextState.started = time.Now() } if win.JustPressed(pixelgl.KeyDown) { if nextState.zIdx <= 0 { log.Printf("z index is already at minimum") } else { nextState.redraw = true nextState.zIdx = nextState.zIdx - 1 } } if win.JustPressed(pixelgl.KeyUp) { if nextState.zIdx >= maps.MaxHeight-1 { log.Printf("z index is already at maximum") } else { nextState.redraw = true nextState.zIdx = nextState.zIdx + 1 } } if win.JustPressed(pixelgl.KeyLeft) { if nextState.cellIdx <= 0 { log.Printf("cell index is already at minimum") } else { nextState.redraw = true nextState.cellIdx = nextState.cellIdx - 1 } } if win.JustPressed(pixelgl.KeyRight) { if nextState.cellIdx >= maps.CellSize-1 { log.Printf("cell index is already at maximum") } else { nextState.redraw = true nextState.cellIdx = nextState.cellIdx + 1 } } // Zoom in and out with the mouse wheel nextState.zoom *= math.Pow(1.2, win.MouseScroll().Y) if nextState.zoom != state.zoom { log.Printf("zoom=%.2f", nextState.zoom) nextState.redraw = true } return &nextState }