From 087b23654713fd6da6863522b536b4951ab3135b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sat, 17 Mar 2018 04:15:40 +0000 Subject: [PATCH] Start investigating the maps --- cmd/load/main.go | 58 ++++++++++++++-- internal/maps/maps.go | 150 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 internal/maps/maps.go diff --git a/cmd/load/main.go b/cmd/load/main.go index f3ff1be..1b645a4 100644 --- a/cmd/load/main.go +++ b/cmd/load/main.go @@ -2,10 +2,12 @@ package main import ( "flag" + "fmt" "log" "path/filepath" "ur.gs/chaos-gate/internal/data" + "ur.gs/chaos-gate/internal/maps" ) var ( @@ -15,13 +17,19 @@ var ( func main() { flag.Parse() + loadData() + loadObj() + + loadMapsFrom("Maps") + loadMapsFrom("MultiMaps") +} + +func loadData() { dataPath := filepath.Join(*gamePath, "Data") accountingPath := filepath.Join(dataPath, "Accounting.dat") genericDataPath := filepath.Join(dataPath, "GenericData.dat") aniObDefPath := filepath.Join(dataPath, "AniObDef.dat") - objDataPath := filepath.Join(*gamePath, "Obj") - log.Printf("Loading %s...", accountingPath) accounting, err := data.LoadAccounting(accountingPath) if err != nil { @@ -45,6 +53,10 @@ func main() { } log.Printf("%s: %+v", genericDataPath, genericData) +} + +func loadObj() { + objDataPath := filepath.Join(*gamePath, "Obj") // TODO: Obj/cpiece.rec isn't loaded by this. Do we need it? How do we know? log.Printf("Loading %s...", objDataPath) @@ -53,11 +65,47 @@ func main() { log.Fatalf("Failed to parse %s: %s", objDataPath, err) } + inspect := "c_webs" + inspect_obj := inspect + ".obj" + log.Printf("Objects in %s:", objDataPath) for key, obj := range objects { - log.Printf("\t%s: %+v", key, obj) + if key != inspect_obj { + continue + } + log.Printf( + "\t%s\t%d frames\t%d bytes", + key, obj.ObjectHeader.NumFrames, obj.ObjectHeader.FrameDataSize, + ) } - log.Printf("Tzeentch: %+v", objects["TZEENTCH.OBJ"]) - log.Printf("TFrame 0: %+v", objects["TZEENTCH.OBJ"].Frames[0]) + log.Printf("%s: %#v", inspect, objects[inspect_obj]) + + for i, frame := range objects[inspect_obj].Frames { + numPixels := frame.Width * frame.Height + log.Printf("frame %d: w=%d h=%d sz=%d w*h=%d bpp=%v", i, frame.Width, frame.Height, frame.PixelSize, numPixels, float64(frame.PixelSize)/float64(numPixels)) + } +} + +func loadMapsFrom(part string) { + mapsPath := filepath.Join(*gamePath, part) + log.Printf("Loading maps from %s", mapsPath) + + gameMaps, err := maps.LoadGameMaps(mapsPath) + if err != nil { + log.Fatalf("Failed to parse %s/*.{MAP,txt} as game maps: %v", mapsPath, err) + } + + log.Printf("Maps in %s:", mapsPath) + for key, gameMap := range gameMaps { + hdr := gameMap.Header + fmt.Printf( + " * `%s`: IsCampaignMap=%v W=%v:%v L=%v:%v SetName=%s\n", + key, + hdr.IsCampaignMap, + hdr.MinWidth, hdr.MaxWidth, + hdr.MinLength, hdr.MaxLength, + string(hdr.SetName[:]), + ) + } } diff --git a/internal/maps/maps.go b/internal/maps/maps.go new file mode 100644 index 0000000..a0bf864 --- /dev/null +++ b/internal/maps/maps.go @@ -0,0 +1,150 @@ +package maps + +import ( + "bytes" + "compress/gzip" + "encoding/binary" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +var ( + expectedMagic = []byte("\x08\x00WHMAP\x00") + expectedSetNameOffset = uint32(0x34) + notImplemented = fmt.Errorf("Not implemented") +) + +type Header struct { + IsCampaignMap uint32 // Tentatively: 0 = no, 1 = yes + MinWidth uint32 + MinLength uint32 + MaxWidth uint32 + MaxLength uint32 + Unknown1 uint32 + Unknown2 uint32 + Unknown3 uint32 + Unknown4 uint32 + Magic [8]byte // "\x08\x00WHMAP\x00" + Unknown5 uint32 + Unknown6 uint32 + SetName [8]byte // Or is it a null-terminated string? +} + +func (h Header) Check() []error { + var out []error + if h.IsCampaignMap > 1 { + out = append(out, fmt.Errorf("Expected 0 or 1 for IsCampaignMap, got %v", h.IsCampaignMap)) + } + + if bytes.Compare(expectedMagic, h.Magic[:]) != 0 { + out = append(out, fmt.Errorf("Unexpected magic value: %v", h.Magic)) + } + + return out +} + +type GameMap struct { + Header + + // TODO: parse this into sections + Text string +} + +// A game map contains a .txt and a .map. If they're in the same directory, +// just pass the directory + basename to load both +func LoadGameMap(prefix string) (*GameMap, error) { + for _, mapExt := range []string{".MAP", ".map"} { + for _, txtExt := range []string{".TXT", ".txt"} { + out, err := LoadGameMapByFiles(prefix+mapExt, prefix+txtExt) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + + if err == nil { + return out, nil + } + } + } + + return nil, fmt.Errorf("Couldn't find %s.{map,txt}, even ignoring case", prefix) +} + +// A game map is composed of two files: .map and .txt +func LoadGameMapByFiles(mapFile, txtFile string) (*GameMap, error) { + out, err := loadMapFile(mapFile) + if err != nil { + return nil, err + } + + // TODO: load text and parse into sections + txt, err := ioutil.ReadFile(txtFile) + if err != nil { + return nil, err + } + out.Text = string(txt) + + for _, err := range out.Check() { + log.Printf("%s: %v", mapFile, err) + } + + return out, nil +} + +func LoadGameMaps(dir string) (map[string]*GameMap, error) { + fis, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + out := make(map[string]*GameMap) + + for _, fi := range fis { + filename := filepath.Join(dir, fi.Name()) + basename := filepath.Base(filename) + extname := filepath.Ext(filename) + + // Only pay attention to .MAP files. Assume they will have a .txt too + if !strings.EqualFold(extname, ".MAP") { + continue + } + + prefix := filename[0 : len(filename)-4] + + gameMap, err := LoadGameMap(prefix) + if err != nil { + return nil, fmt.Errorf("Error processing %v: %s", filename, err) + } + + out[basename] = gameMap + } + + return out, nil +} + +func loadMapFile(filename string) (*GameMap, error) { + var out GameMap + + mf, err := os.Open(filename) + if err != nil { + return nil, err + } + + defer mf.Close() + + zr, err := gzip.NewReader(mf) + if err != nil { + return nil, err + } + + defer zr.Close() + + if err := binary.Read(zr, binary.LittleEndian, &out.Header); err != nil { + return nil, fmt.Errorf("Error parsing %s: %v", filename, err) + } + + return &out, nil +}