diff --git a/doc/formats/maps.md b/doc/formats/maps.md index 2432703..7bd2fa8 100644 --- a/doc/formats/maps.md +++ b/doc/formats/maps.md @@ -598,7 +598,8 @@ Ignoring them for now, here's a first guess at a header: | 24 | 2 | ??? - varies. Seems related to character/squad position? | | 26 | 2 | ??? - invariant `00 04` | | 28 | 4 | ??? - varies (0 vs 5) | -| 32 | 4 | Number of thingies (26 vs 1) | +| 32 | 1 | Number of thingies | +| 33 | 3 | ???. With a Lord of Change on the map, only one byte works as thingies count | | 36 | 20 | Padding? | 56 bytes of data is interesting because the value of that first, ignored byte is @@ -625,7 +626,78 @@ Generating a map with no characters at all, the trailer is 2,447 bytes, and the mission title starts at 0x3B (59). So we can say we have 20 bytes of padding as a first approximation? -The "trailer trailer", for want of a better term, seems to be organised as: +Here's where we're at with the per-character data, going from the padding values +suggested above: + +| Offset | Size | Meaning | +| ------ | ---- | ------- | +| 0 | 179 | ??? | +| 179 | 80 | Character name | +| 259 | 10 | Character attributes | +| 269 | 495 | ??? | +| 764 | 1(?) | Squad number? | +| 765 | 927 | ??? | + +There's still a lot of bytes to dig through, but this allows me to load the +character names from Chapter01 correctly, with the exception of record 57 which +just contains `\x02` and is then null-terminated all the way through. Looking +at the bytes for one character record, I can easily correlate certain bytes to +various attributes; that's just done in code for the moment. + +Given two characters of the same time, just in different locations, differing +values are seen at: + +* `0x103 - 0x10c` (hodgepodge) +* `0x178 - 0x1be` (hodgepodge) +* `0x2fc` (0, 1) - squad number? + +In Chapter01, picking a random character (Gorgon) and looking at his squadmates, +they are all in the same squad, and no other characters are in that squad, so it +looks pretty diagnostic to me. There's nothing in the UI to indicate the squad, +though. + +Now let's look for position. In my 2-character map, they're at 65,50 and 70,55. +Within a character, I see those numbers repeated twice - around `0x1b{9,a}` and +`0x1b{d,e}`. + +Characters don't seem to take up multiple x,y squares, but they *can* take up +multiple Z levels. So maybe this is a bounding box of some kind? + +Adding a (tall) Lord of Change to the map gave me `02 36 45 00 02 37 45`, which +doesn't quite match what my eyes are telling me for Z,Y,X. In addition, the data +immediately after this offset changed into a large number of coordinate-like +sets of values - far too many for it to actually be a bounding box. However, the +first one remains good as a position specifier. + +Down in `0x679` (Chaos Sorcerer) or `0x68D` (Lord of Change), the map coords for +the *other* character appears, which is downright odd. For now, just use the +first-indexed value. + +How about their types? We need to be able to work out which animations to show, +so they must be uniquely identified somehow. Ultramarine captain is animation +group 12, while chaos lord is group... 14? Not certain. I don't see either of +those numbers in a promising spot, anyway. + + +I do see differences at around `0x170`: + +``` +Ultramarine Captain: 00 00 00 00 00 00 00 00 50 03 00 00 00 00 00 00 +Chaos Lord: 00 00 00 00 11 01 00 00 47 03 00 04 00 00 02 00 +``` + +Maybe they're somewhat relevant, it's hard to say. + +Thingies next: these aren't decoded at all yet, and the sizes seem to be +variable. + +| Offset | Size | Meaning | +| ------ | ---- | ------- | +| | | | + + +Finally, the "trailer trailer", for want of a better term, seems to be organised +as: | Offset | Size | Meaning | | ----- | ---- | ------- | @@ -633,6 +705,8 @@ The "trailer trailer", for want of a better term, seems to be organised as: | 255 | 2048 | Briefing | | 2304 | 85 | ??? - each byte is 1 or 0. Spaced so it may be partly uint32 | +This duplicates the information found in the `.TXT` files. No idea what the end +data is yet. ## Soldiers At War diff --git a/internal/assetstore/map.go b/internal/assetstore/map.go index b38a946..b2ac8d4 100644 --- a/internal/assetstore/map.go +++ b/internal/assetstore/map.go @@ -102,5 +102,21 @@ func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) { sprites = append(sprites, sprite) } + // FIXME: this just marks character positions with sprite 19 for now. + specialsObj, err := m.assets.Object("specials") + if err != nil { + return nil, err + } + chrSpr, err := specialsObj.Sprite(19) + if err != nil { + return nil, err + } + + for _, chr := range m.raw.Characters { + if chr.XPos == x && chr.YPos == y && z == 1 { // FIXME: sort out ZPos + sprites = append(sprites, chrSpr) + } + } + return sprites, nil } diff --git a/internal/maps/maps.go b/internal/maps/maps.go index b6f92f8..7bb16f8 100644 --- a/internal/maps/maps.go +++ b/internal/maps/maps.go @@ -67,8 +67,9 @@ type GameMap struct { TrailerUnknown3 int `struc:"uint16"` TrailerUnknown4 int `struc:"uint32"` - NumThingies int `struc:"uint32"` - Padding1 []byte `struc:"[20]byte"` + NumThingies int `struc:"byte"` + TrailerUnknown5 []byte `struc:"[3]byte"` + Padding1 []byte `struc:"[20]byte"` // FIXME: The rest is trash until Character & Thingy are worked out @@ -79,7 +80,7 @@ type GameMap struct { Briefing string `struc:"[2048]byte"` // Maybe? each contains either 0 or 1? Hard to say - TrailerUnknown5 []byte `struc:"[85]byte"` + TrailerUnknown6 []byte `struc:"[85]byte"` } type Cell struct { @@ -98,7 +99,29 @@ type Cell struct { } type Character struct { - Unknown1 int `struc:"uint32"` + Unknown1 []byte `struc:"[179]byte"` + Name string `struc:"[80]byte"` + + // Attributes guessed by matching up numbers. Starts at 0x103 + WeaponSkill int `struc:"byte"` + BallisticSkill int `struc:"byte"` + Unknown2 byte `struc:"byte"` + Leadership int `struc:"byte"` + Toughness int `struc:"byte"` + Strength int `struc:"byte"` + ActionPoints int `struc:"byte"` + Unknown3 byte `struc:"byte"` + Unknown4 byte `struc:"byte"` + Health int `struc:"byte"` + + Unknown5 []byte `struc:"[91]byte"` + Armor int `struc:"byte"` + Unknown6 []byte `struc:"[84]byte"` + YPos int `struc:"byte"` // These are actually much more complicated + XPos int `struc:"byte"` + Unknown7 []byte `struc:"[317]byte"` + SquadNumber byte `struc:"byte"` + Unknown8 []byte `struc:"[927]byte"` // TODO: each character may have a fixed number of subrecords for inventory } @@ -289,13 +312,38 @@ func loadMapFile(filename string) (*GameMap, error) { } // Trim any trailing nulls off of the strings - trimRight(&out.SetName) - trimRight(&out.Title) - trimRight(&out.Briefing) + nullTerminate(&out.SetName) + nullTerminate(&out.Title) + nullTerminate(&out.Briefing) + + for i, chr := range out.Characters { + nullTerminate(&chr.Name) + fmt.Printf("Character %v: %s\n", i, chr.String()) + } + + fmt.Printf("Mission Title: %q\n", out.Title) + fmt.Printf("Mission Briefing: %q\n", out.Briefing) return &out, nil } -func trimRight(s *string) { - *s = strings.TrimRight(*s, "\x00") +func nullTerminate(s *string) { + sCpy := *s + idx := strings.Index(sCpy, "\x00") + if idx < 0 { + return + } + + *s = sCpy[0:idx] +} + +func (c *Character) String() string { + return fmt.Sprintf( + "squad=%v pos=(%v,%v) name=%q\n\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n", + c.SquadNumber, + c.XPos, c.YPos, + c.Name, + c.ActionPoints, c.Health, c.Armor, c.BallisticSkill, c.WeaponSkill, + c.Strength, c.Toughness /*c.Initiative, c.Attacks,*/, c.Leadership, + ) }