Files
ordoor/internal/maps/maps.go

262 lines
5.5 KiB
Go
Raw Normal View History

2018-03-17 04:15:40 +00:00
package maps
import (
"bytes"
"compress/gzip"
"encoding/binary"
"fmt"
"io"
2018-03-17 04:15:40 +00:00
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
var (
expectedMagic = []byte("\x08\x00WHMAP\x00")
expectedSetNameOffset = uint32(0x34)
notImplemented = fmt.Errorf("Not implemented")
)
const (
MaxHeight = 7 // Z coordinate
MaxLength = 100 // Y coordinate
MaxWidth = 130 // X coordinate
CellSize = 16 // seems to be
2018-03-18 13:57:01 +00:00
cellDataOffset = 0x120 // tentatively
cellCount = MaxHeight * MaxLength * MaxWidth
)
2018-03-17 04:15:40 +00:00
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 // Links to a filename in `/Sets/*.set`
// Need to investigate the rest of the header too
}
func (h Header) Width() int {
return int(h.MaxWidth - h.MinWidth)
}
func (h Header) Length() int {
return int(h.MaxLength - h.MinLength)
}
func (h Header) Height() int {
return MaxHeight
}
2018-03-18 13:57:01 +00:00
func (h Header) MapSetFilename() string {
idx := bytes.IndexByte(h.SetName[:], 0)
if idx < 0 {
idx = 8 // all 8 bytes are used
}
return string(h.SetName[0:idx:idx]) + ".set"
}
type Cell struct {
DoorAndCanisterRelated byte
DoorLockAndReactorRelated byte
Unknown2 byte
Object0SurfaceArea byte
Unknown4 byte
Object1LeftArea byte
Unknown6 byte
Object2RightArea byte
Unknown8 byte
Object3CenterArea byte
Unknown10 byte
Unknown11 byte
Unknown12 byte
Unknown13 byte
Unknown14 byte
SquadRelated byte
}
func (c *Cell) At(n int) byte {
switch n {
case 0:
return c.DoorAndCanisterRelated
case 1:
return c.DoorLockAndReactorRelated
case 2:
return c.Unknown2
case 3:
return c.Object0SurfaceArea
case 4:
return c.Unknown4
case 5:
return c.Object1LeftArea
case 6:
return c.Unknown6
case 7:
return c.Object2RightArea
case 8:
return c.Unknown8
case 9:
return c.Object3CenterArea
case 10:
return c.Unknown10
case 11:
return c.Unknown11
case 12:
return c.Unknown12
case 13:
return c.Unknown13
case 14:
return c.Unknown14
case 15:
return c.SquadRelated
}
return 0
}
// Cells is always a fixed size; use At to get a cell according to x,y,z
2018-03-18 13:57:01 +00:00
type Cells []Cell
// FIXME: Ordering may be incorrect? I assume z,y,x for now...
func (c Cells) At(x, y, z int) Cell {
2018-03-18 13:57:01 +00:00
return c[(z*MaxLength*MaxWidth)+(y*MaxWidth)+x]
2018-03-17 04:15:40 +00:00
}
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
Cells
2018-03-17 04:15:40 +00:00
// 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 _, txtExt := range []string{".TXT", ".txt"} {
for _, mapExt := range []string{".MAP", ".map"} {
2018-03-17 04:15:40 +00:00
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)
}
// no gzip.SeekReader, so discard unread header bytes for now
discardSize := int64(cellDataOffset - binary.Size(&out.Header))
if _, err := io.CopyN(ioutil.Discard, zr, discardSize); err != nil {
return nil, err
}
2018-03-18 13:57:01 +00:00
out.Cells = make(Cells, cellCount)
if err := binary.Read(zr, binary.LittleEndian, &out.Cells); err != nil {
2018-03-18 13:57:01 +00:00
return nil, fmt.Errorf("Error parsing cells for %s: %v", filename, err)
}
2018-03-17 04:15:40 +00:00
return &out, nil
}