diff --git a/cmd/load/main.go b/cmd/load/main.go index 1b645a4..80ccc80 100644 --- a/cmd/load/main.go +++ b/cmd/load/main.go @@ -8,6 +8,7 @@ import ( "ur.gs/chaos-gate/internal/data" "ur.gs/chaos-gate/internal/maps" + "ur.gs/chaos-gate/internal/sets" ) var ( @@ -22,6 +23,7 @@ func main() { loadMapsFrom("Maps") loadMapsFrom("MultiMaps") + loadSets() } func loadData() { @@ -109,3 +111,17 @@ func loadMapsFrom(part string) { ) } } + +func loadSets() { + setsPath := filepath.Join(*gamePath, "Sets") + log.Printf("Loading sets from %s", setsPath) + + mapSets, err := sets.LoadSets(setsPath) + if err != nil { + log.Fatalf("Failed to parse %s/*.set as map sets: %v", setsPath, err) + } + + for key, mapSet := range mapSets { + fmt.Printf(" * `%s`: Defs=%#v len(palette)=%d\n", key, mapSet.Defs, len(mapSet.Palette)) + } +} diff --git a/doc/formats/index.md b/doc/formats/index.md index 194bff8..37a14c6 100644 --- a/doc/formats/index.md +++ b/doc/formats/index.md @@ -89,7 +89,7 @@ remake. * `Save_G/` * `*.sav` # savedata, gzip-compressed, custom format * `*.txt` # Seems to be a copy of one of Maps/*.txt -* `Sets/` +* [`Sets/`](sets.md) * `Data.chk` # checksums? Mentions all the .set files * `*.set` # plain text, related to maps. Editor has a concept of map sets, which these must be * [✓] `SMK/` diff --git a/doc/formats/sets.md b/doc/formats/sets.md new file mode 100644 index 0000000..906fbee --- /dev/null +++ b/doc/formats/sets.md @@ -0,0 +1,77 @@ +# Set format information + +`*.set` files seem to act as a palette of objects for a [`.map`](maps.md) file. +The map file loads a set, and can then reference objects by a small number. + +Since a maximally-filled map file seems to be able to reference 91,000 * 4 +objects, this is a necessary optimization for 1998-era hardware. + +## Structure + +These files are plain-text. + +We handily have a `template.set`, which looks like: + +``` +set template + +Defs + +40 40 40 80 + + +dirt +h_dirt +rocks +blank +blank +blank +blank +blank +blank +blank # +blank +blank +blank +blank +blank +blank +blank +blank +blank +# ... +``` + +The files are of varying lengths. `template.set` is 220 lines, `map10.set` only +83. + +So it's a line-based format that goes: + +* Set description +* Blank line (optional, missing in `GEN_JUN.set`, `GEN_WAS.set`, others) +* `Defs` +* Blank line +* 4 space-separated numbers, variable between sets +* At least one blank line, sometimes 2 +* A list of object names, sometimes with # comments on the right hand side +* They all seem to end with a comment of some sort, e.g. `# meaningless comment` + +Questions: + +What are the `Defs` for? Is it `Defaults` or `Definitions`? The values are +quite variable between files. + +Is whitespace significant in the list of objects? First assumption is no. + +Is it a simple 0-indexed palette or do maps embed an absolute line number? + +Do positions in the palette have special meaning? e.g. is a particular range +always reserved for walls? + +Are there any special values that don't appear as files in the `Obj/` directory? +`blank.obj` exists, so I expect not. + +Once the map format is fleshed out a little more, can investigate by creating a +map with a single object from the set in it and seeing what line that works out +to be. + diff --git a/internal/sets/sets.go b/internal/sets/sets.go new file mode 100644 index 0000000..a64924e --- /dev/null +++ b/internal/sets/sets.go @@ -0,0 +1,120 @@ +package sets + +import ( + "fmt" + "io" + "io/ioutil" + "path/filepath" + "strconv" + "strings" + + "ur.gs/chaos-gate/internal/util/asciiscan" +) + +type MapSet struct { + Description string + Defs [4]byte // TODO: work out what these are for + + // TODO: is there any more structure than this? Should I preserve empty lines? + Palette []string +} + +func LoadSets(dir string) (map[string]MapSet, error) { + fis, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + out := make(map[string]MapSet, len(fis)) + + for _, fi := range fis { + filename := filepath.Join(dir, fi.Name()) + basename := filepath.Base(filename) + extname := filepath.Ext(filename) + + // Don't try to load non-.obj files + if !strings.EqualFold(extname, ".set") { + continue + } + + obj, err := LoadSet(filename) + if err != nil { + return nil, fmt.Errorf("%s: %v", filename, err) + } + + out[basename] = obj + } + + return out, nil +} + +func LoadSet(filename string) (MapSet, error) { + var out MapSet + var err error + + s, err := asciiscan.New(filename) + if err != nil { + return out, err + } + + defer s.Close() + + out.Description, err = s.ConsumeString() + if err != nil { + return out, err + } + + out.Defs, err = consumeDefs(s) + if err != nil { + return out, err + } + + for { + str, err := s.ConsumeString() + if err != nil { + if err == io.EOF { + err = nil + } + + return out, err + } + + out.Palette = append(out.Palette, str) + } + + return out, nil +} + +func consumeDefs(scanner *asciiscan.Scanner) ([4]byte, error) { + var out [4]byte + + expectDefs, err := scanner.ConsumeString() + if err != nil { + return out, err + } + + if expectDefs != "Defs" { + return out, fmt.Errorf("Couldn't find Defs section") + } + + defs, err := scanner.ConsumeString() + if err != nil { + return out, err + } + + parts := strings.SplitN(defs, " ", -1) + if len(parts) != 4 { + return out, fmt.Errorf("Defs section did not have 4 components") + } + + for i, part := range parts { + n, err := strconv.ParseInt(part, 10, 8) + if err != nil { + return out, err + } + + out[i] = byte(n) // safe as we specify 8 bits to ParseInt + } + + return out, nil +}