From 32fd9f9aa9d65678c9783e210cb32b1d8cfa83f4 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 15 Apr 2020 21:11:01 +0100 Subject: [PATCH] More investigation into animation --- cmd/loader/main.go | 19 +- doc/formats/ani.md | 315 +++++++++++++++++---------- doc/formats/index.md | 10 +- go.mod | 1 + go.sum | 2 + internal/data/has_action.go | 178 +++++++++++++++ internal/idx/idx.go | 73 +++++++ internal/menus/menus.go | 12 +- internal/util/asciiscan/asciiscan.go | 29 +++ 9 files changed, 515 insertions(+), 124 deletions(-) create mode 100644 internal/data/has_action.go create mode 100644 internal/idx/idx.go diff --git a/cmd/loader/main.go b/cmd/loader/main.go index ee41102..ade179e 100644 --- a/cmd/loader/main.go +++ b/cmd/loader/main.go @@ -9,6 +9,7 @@ import ( "code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/fonts" + "code.ur.gs/lupine/ordoor/internal/idx" "code.ur.gs/lupine/ordoor/internal/maps" "code.ur.gs/lupine/ordoor/internal/menus" "code.ur.gs/lupine/ordoor/internal/sets" @@ -33,13 +34,15 @@ func main() { loadSets() loadMenus() loadFonts() + loadIdx() } 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") + genericDataPath := filepath.Join(dataPath, "GenericData.dat") + hasActionPath := filepath.Join(dataPath, "HasAction.dat") i18nPath := filepath.Join(dataPath, data.I18nFile) log.Printf("Loading %s...", accountingPath) @@ -73,6 +76,12 @@ func loadData() { } log.Printf("%s: len=%v", i18nPath, i18n.Len()) + + ha, err := data.LoadHasAction(hasActionPath) + if err != nil { + log.Fatalf("Failed to parse %s: %v", hasActionPath, err) + } + ha.Print() } func loadObj() { @@ -189,3 +198,11 @@ func loadFonts() { fmt.Printf(" * `%s`: obj=%v entries=%v\n", font.Name, font.ObjectFile, font.Entries()) } } + +func loadIdx() { + idxPath := filepath.Join(*gamePath, "Idx", "WarHammer.idx") + _, err := idx.Load(idxPath) + if err != nil { + log.Fatalf("Failed to parse %s as idx: %v", idxPath, err) + } +} diff --git a/doc/formats/ani.md b/doc/formats/ani.md index 6cd11be..3100efb 100644 --- a/doc/formats/ani.md +++ b/doc/formats/ani.md @@ -162,6 +162,7 @@ how many type 2 records there are. | 0 | 2? | `02 06` - ??? | | 2 | 2? | `n xx` - ??? `n` counts from 1 to 8 | | 4 | 4 | Position of type 3 record | +| 8 | 4 | ??? | The two type 3a and 3b records are identical to each other, it's hard to know what's what. @@ -169,137 +170,219 @@ what's what. None of the unknown numbers, however sliced, seem to be sprite indices for `Anim/WarHammer.ani`. -Here's a trace of both `idx` (fd 34) and `ani` (fd 14) files when placing a -single librarian for me to dig into in more detail. +Here's a trace of both `idx` and `ani` files when placing a single librarian for +me to dig into in more detail. No other files are touched when doing this.
``` -read(14, "~\337\2\0 \0\0\0\360\373\26\0\20\374\26\0\245J\303\30\0\0\0\0\0\0\0\0(\222R\0", 32) = 32 +_llseek(, 132, [132], SEEK_SET) = 0 +read(, "\x30\x7c\x09\x00\x98\x00\x00\x00\x88\xf8\x00\x00", 12) = 12 -_llseek(34, 132, [132], SEEK_SET) = 0 -read(34, "0|\t\0\230\0\0\0\210\370\0\0", 12) = 12 +_llseek(, 132, [132], SEEK_SET) = 0 +read(, "\x30\x7c\x09\x00\x98\x00\x00\x00\x88\xf8\x00\x00", 12) = 12 -_llseek(34, 132, [132], SEEK_SET) = 0 -read(34, "0|\t\0\230\0\0\0\210\370\0\0", 12) = 12 +_llseek(, 621616, [621616], SEEK_SET) = 0 +read(, "\x02\x01\x01\x33\x50\x83\x09\x00\x0d\x00\x00\x00", 12) = 12 -_llseek(34, 621616, [621616], SEEK_SET) = 0 -read(34, "\2\1\0013P\203\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621628, [621628], SEEK_SET) = 0 -read(34, "\2\1\0023\262\203\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621640, [621640], SEEK_SET) = 0 -read(34, "\2\1\0033\24\204\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621652, [621652], SEEK_SET) = 0 -read(34, "\2\1\0043v\204\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621664, [621664], SEEK_SET) = 0 -read(34, "\2\1\0053\330\204\t\0\r\0\0\0", 12) = 12 +_llseek(, 621628, [621628], SEEK_SET) = 0 +read(, "\x02\x01\x02\x33\xb2\x83\x09\x00\x0d\x00\x00\x00", 12) = 12 -_llseek(34, 623832, [623832], SEEK_SET) = 0 -read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20 +_llseek(, 621640, [621640], SEEK_SET) = 0 +read(, "\x02\x01\x03\x33\x14\x84\x09\x00\x0d\x00\x00\x00", 12) = 12 -_llseek(14, 509440, [509440], SEEK_SET) = 0 -read(14, "\fM\266\th\16\0\0", 8) = 8 -_llseek(14, 509448, [509448], SEEK_SET) = 0 -read(14, "t[\266\t\376\16\0\0", 8) = 8 -_llseek(14, 509456, [509456], SEEK_SET) = 0 -read(14, "rj\266\tg\17\0\0", 8) = 8 -_llseek(14, 509464, [509464], SEEK_SET) = 0 -read(14, "\331y\266\t\251\17\0\0", 8) = 8 -_llseek(14, 509472, [509472], SEEK_SET) = 0 -read(14, "\202\211\266\t\273\17\0\0", 8) = 8 -_llseek(14, 509480, [509480], SEEK_SET) = 0 -read(14, "=\231\266\t\10\20\0\0", 8) = 8 -_llseek(14, 509488, [509488], SEEK_SET) = 0 -read(14, "E\251\266\t\321\17\0\0", 8) = 8 -_llseek(14, 509496, [509496], SEEK_SET) = 0 -read(14, "\26\271\266\t\1\17\0\0", 8) = 8 -_llseek(14, 509504, [509504], SEEK_SET) = 0 -read(14, "\27\310\266\t\304\16\0\0", 8) = 8 -_llseek(14, 509512, [509512], SEEK_SET) = 0 -read(14, "\333\326\266\t\343\16\0\0", 8) = 8 -_llseek(14, 509520, [509520], SEEK_SET) = 0 -read(14, "\276\345\266\t\f\17\0\0", 8) = 8 -_llseek(14, 509528, [509528], SEEK_SET) = 0 -read(14, "\312\364\266\tA\17\0\0", 8) = 8 -_llseek(14, 509536, [509536], SEEK_SET) = 0 -read(14, "\v\4\267\t\246\17\0\0", 8) = 8 -_llseek(14, 509440, [509440], SEEK_SET) = 0 -read(14, "\fM\266\th\16\0\0", 8) = 8 +_llseek(, 621652, [621652], SEEK_SET) = 0 +read(, "\x02\x01\x04\x33\x76\x84\x09\x00\x0d\x00\x00\x00", 12) = 12 -_llseek(14, 164448540, [164448540], SEEK_SET) = 0 -read(14, "\367\0\n\0015\0T\0\0\0\0\0P\16\0\0\324q;\1\0\0\0\0\200\23\207**+*+"..., 3688) = 3688 -_llseek(14, 509448, [509448], SEEK_SET) = 0 -read(14, "t[\266\t\376\16\0\0", 8) = 8 -_llseek(14, 164452228, [164452228], SEEK_SET) = 0 -read(14, "\365\0\10\0017\0W\0\0\0\0\0\346\16\0\0\324q;\1\0\0\0\0\200\25\3*\212+*,"..., 3838) = 3838 -_llseek(14, 509456, [509456], SEEK_SET) = 0 -read(14, "rj\266\tg\17\0\0", 8) = 8 -_llseek(14, 164456066, [164456066], SEEK_SET) = 0 -read(14, "\364\0\10\19\0Z\0\0\0\0\0O\17\0\0\324q;\1\0\0\0\0\200\30\201*\5+\200\33"..., 3943) = 3943 -_llseek(14, 509464, [509464], SEEK_SET) = 0 -read(14, "\331y\266\t\251\17\0\0", 8) = 8 -_llseek(14, 164460009, [164460009], SEEK_SET) = 0 -read(14, "\356\0\7\1B\0[\0\0\0\0\0\221\17\0\0\324q;\1\0\0\0\0\200\"\201*\200\37\0\200"..., 4009) = 4009 -_llseek(14, 509472, [509472], SEEK_SET) = 0 -read(14, "\202\211\266\t\273\17\0\0", 8) = 8 -_llseek(14, 164464018, [164464018], SEEK_SET) = 0 -read(14, "\356\0\n\1C\0\\\0\0\0\0\0\243\17\0\0\324q;\1\0\0\0\0\200#\3)\3+\200\32"..., 4027) = 4027 -_llseek(14, 509480, [509480], SEEK_SET) = 0 -read(14, "=\231\266\t\10\20\0\0", 8) = 8 -_llseek(14, 164468045, [164468045], SEEK_SET) = 0 -read(14, "\354\0\t\1C\0Z\0\0\0\0\0\360\17\0\0\324q;\1\0\0\0\0\200$\201*\200\36\0\200"..., 4104) = 4104 -_llseek(14, 509488, [509488], SEEK_SET) = 0 -read(14, "E\251\266\t\321\17\0\0", 8) = 8 -_llseek(14, 164472149, [164472149], SEEK_SET) = 0 -read(14, "\356\0\t\1?\0V\0\0\0\0\0\271\17\0\0\324q;\1\0\0\0\0\200\35\212&&H)*"..., 4049) = 4049 -_llseek(14, 509496, [509496], SEEK_SET) = 0 -read(14, "\26\271\266\t\1\17\0\0", 8) = 8 -_llseek(14, 164476198, [164476198], SEEK_SET) = 0 -read(14, "\366\0\10\0015\0[\0\0\0\0\0\351\16\0\0\324q;\1\0\0\0\0\200\20\201*\4+\206*"..., 3841) = 3841 -_llseek(14, 509504, [509504], SEEK_SET) = 0 -read(14, "\27\310\266\t\304\16\0\0", 8) = 8 -_llseek(14, 164480039, [164480039], SEEK_SET) = 0 -read(14, "\367\0\7\0013\0[\0\0\0\0\0\254\16\0\0\324q;\1\0\0\0\0\200\33\210+,+,,"..., 3780) = 3780 -_llseek(14, 509512, [509512], SEEK_SET) = 0 -read(14, "\333\326\266\t\343\16\0\0", 8) = 8 -_llseek(14, 164483819, [164483819], SEEK_SET) = 0 -read(14, "\370\0\7\1A\0_\0\0\0\0\0\313\16\0\0\324q;\1\0\0\0\0\200\34\203,,*\200\""..., 3811) = 3811 -_llseek(14, 509520, [509520], SEEK_SET) = 0 -read(14, "\276\345\266\t\f\17\0\0", 8) = 8 -_llseek(14, 164487630, [164487630], SEEK_SET) = 0 -read(14, "\370\0\7\1H\0`\0\0\0\0\0\364\16\0\0\324q;\1\0\0\0\0\200\35\201,\200*\0\200"..., 3852) = 3852 -_llseek(14, 509528, [509528], SEEK_SET) = 0 -read(14, "\312\364\266\tA\17\0\0", 8) = 8 -_llseek(14, 164491482, [164491482], SEEK_SET) = 0 -read(14, "\373\0\10\1;\0Z\0\0\0\0\0)\17\0\0\324q;\1\0\0\0\0\200\36\202+,\200\33\0"..., 3905) = 3905 -_llseek(14, 509536, [509536], SEEK_SET) = 0 -read(14, "\v\4\267\t\246\17\0\0", 8) = 8 -_llseek(14, 164495387, [164495387], SEEK_SET) = 0 -read(14, "\366\0\n\18\0Y\0\0\0\0\0\216\17\0\0\324q;\1\0\0\0\0\200\32\t+\205*\4,"..., 4006) = 4006 +_llseek(, 621664, [621664], SEEK_SET) = 0 +read(, "\x02\x01\x05\x33\xd8\x84\x09\x00\x0d\x00\x00\x00", 12) = 12 -_llseek(34, 623832, [623832], SEEK_SET) = 0 -read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20 -read(34, "\0\0\6\0\4\0\0\0\6\0\4\0\0\0\6\0\4\0\0\0\5\0\4\0\0\0\5\0\4\0\0\0"..., 78) = 78 +_llseek(, 623832, [623832], SEEK_SET) = 0 +read(, "\x34\x00\x40\x00\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 20) = 20 -_llseek(34, 132, [132], SEEK_SET) = 0 -read(34, "0|\t\0\230\0\0\0\210\370\0\0", 12) = 12 +_llseek(, 0, [623852], SEEK_CUR) = 0 +_llseek(, 623852, [623852], SEEK_SET) = 0 +_llseek(, 623930, [623930], SEEK_SET) = 0 -_llseek(34, 621616, [621616], SEEK_SET) = 0 -read(34, "\2\1\0013P\203\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621628, [621628], SEEK_SET) = 0 -read(34, "\2\1\0023\262\203\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621640, [621640], SEEK_SET) = 0 -read(34, "\2\1\0033\24\204\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621652, [621652], SEEK_SET) = 0 -read(34, "\2\1\0043v\204\t\0\r\0\0\0", 12) = 12 -_llseek(34, 621664, [621664], SEEK_SET) = 0 -read(34, "\2\1\0053\330\204\t\0\r\0\0\0", 12) = 12 +_llseek(, 509440, [509440], SEEK_SET) = 0 +read(, "\x0c\x4d\xb6\x09\x68\x0e\x00\x00", 8) = 8 + +_llseek(, 509448, [509448], SEEK_SET) = 0 +read(, "\x74\x5b\xb6\x09\xfe\x0e\x00\x00", 8) = 8 + +_llseek(, 509456, [509456], SEEK_SET) = 0 +read(, "\x72\x6a\xb6\x09\x67\x0f\x00\x00", 8) = 8 + +_llseek(, 509464, [509464], SEEK_SET) = 0 +read(, "\xd9\x79\xb6\x09\xa9\x0f\x00\x00", 8) = 8 + +_llseek(, 509472, [509472], SEEK_SET) = 0 +read(, "\x82\x89\xb6\x09\xbb\x0f\x00\x00", 8) = 8 + +_llseek(, 509480, [509480], SEEK_SET) = 0 +read(, "\x3d\x99\xb6\x09\x08\x10\x00\x00", 8) = 8 + +_llseek(, 509488, [509488], SEEK_SET) = 0 +read(, "\x45\xa9\xb6\x09\xd1\x0f\x00\x00", 8) = 8 + +_llseek(, 509496, [509496], SEEK_SET) = 0 +read(, "\x16\xb9\xb6\x09\x01\x0f\x00\x00", 8) = 8 + +_llseek(, 509504, [509504], SEEK_SET) = 0 +read(, "\x17\xc8\xb6\x09\xc4\x0e\x00\x00", 8) = 8 + +_llseek(, 509512, [509512], SEEK_SET) = 0 +read(, "\xdb\xd6\xb6\x09\xe3\x0e\x00\x00", 8) = 8 + +_llseek(, 509520, [509520], SEEK_SET) = 0 +read(, "\xbe\xe5\xb6\x09\x0c\x0f\x00\x00", 8) = 8 + +_llseek(, 509528, [509528], SEEK_SET) = 0 +read(, "\xca\xf4\xb6\x09\x41\x0f\x00\x00", 8) = 8 + +_llseek(, 509536, [509536], SEEK_SET) = 0 +read(, "\x0b\x04\xb7\x09\xa6\x0f\x00\x00", 8) = 8 + +_llseek(, 509440, [509440], SEEK_SET) = 0 +read(, "\x0c\x4d\xb6\x09\x68\x0e\x00\x00", 8) = 8 + +_llseek(, 164448540, [164448540], SEEK_SET) = 0 +read(, "\xf7\x00\x0a\x01\x35\x00\x54\x00\x00\x00\x00\x00\x50\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x13\x87\x2a\x2a\x2b\x2a\x2b"..., 3688) = 3688 + +_llseek(, 509448, [509448], SEEK_SET) = 0 +read(, "\x74\x5b\xb6\x09\xfe\x0e\x00\x00", 8) = 8 + +_llseek(, 164452228, [164452228], SEEK_SET) = 0 +read(, "\xf5\x00\x08\x01\x37\x00\x57\x00\x00\x00\x00\x00\xe6\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x15\x03\x2a\x8a\x2b\x2a\x2c"..., 3838) = 3838 + +_llseek(, 509456, [509456], SEEK_SET) = 0 +read(, "\x72\x6a\xb6\x09\x67\x0f\x00\x00", 8) = 8 + +_llseek(, 164456066, [164456066], SEEK_SET) = 0 +read(, "\xf4\x00\x08\x01\x39\x00\x5a\x00\x00\x00\x00\x00\x4f\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x18\x81\x2a\x05\x2b\x80\x1b"..., 3943) = 3943 + +_llseek(, 509464, [509464], SEEK_SET) = 0 +read(, "\xd9\x79\xb6\x09\xa9\x0f\x00\x00", 8) = 8 + +_llseek(, 164460009, [164460009], SEEK_SET) = 0 +read(, "\xee\x00\x07\x01\x42\x00\x5b\x00\x00\x00\x00\x00\x91\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x22\x81\x2a\x80\x1f\x00\x80"..., 4009) = 4009 + +_llseek(, 509472, [509472], SEEK_SET) = 0 +read(, "\x82\x89\xb6\x09\xbb\x0f\x00\x00", 8) = 8 + +_llseek(, 164464018, [164464018], SEEK_SET) = 0 +read(, "\xee\x00\x0a\x01\x43\x00\x5c\x00\x00\x00\x00\x00\xa3\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x23\x03\x29\x03\x2b\x80\x1a"..., 4027) = 4027 + +_llseek(, 509480, [509480], SEEK_SET) = 0 +read(, "\x3d\x99\xb6\x09\x08\x10\x00\x00", 8) = 8 + +_llseek(, 164468045, [164468045], SEEK_SET) = 0 +read(, "\xec\x00\x09\x01\x43\x00\x5a\x00\x00\x00\x00\x00\xf0\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x24\x81\x2a\x80\x1e\x00\x80"..., 4104) = 4104 + +_llseek(, 509488, [509488], SEEK_SET) = 0 +read(, "\x45\xa9\xb6\x09\xd1\x0f\x00\x00", 8) = 8 + +_llseek(, 164472149, [164472149], SEEK_SET) = 0 +read(, "\xee\x00\x09\x01\x3f\x00\x56\x00\x00\x00\x00\x00\xb9\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1d\x8a\x26\x26\x48\x29\x2a"..., 4049) = 4049 + +_llseek(, 509496, [509496], SEEK_SET) = 0 +read(, "\x16\xb9\xb6\x09\x01\x0f\x00\x00", 8) = 8 + +_llseek(, 164476198, [164476198], SEEK_SET) = 0 +read(, "\xf6\x00\x08\x01\x35\x00\x5b\x00\x00\x00\x00\x00\xe9\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x10\x81\x2a\x04\x2b\x86\x2a"..., 3841) = 3841 + +_llseek(, 509504, [509504], SEEK_SET) = 0 +read(, "\x17\xc8\xb6\x09\xc4\x0e\x00\x00", 8) = 8 + +_llseek(, 164480039, [164480039], SEEK_SET) = 0 +read(, "\xf7\x00\x07\x01\x33\x00\x5b\x00\x00\x00\x00\x00\xac\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1b\x88\x2b\x2c\x2b\x2c\x2c"..., 3780) = 3780 + +_llseek(, 509512, [509512], SEEK_SET) = 0 +read(, "\xdb\xd6\xb6\x09\xe3\x0e\x00\x00", 8) = 8 + +_llseek(, 164483819, [164483819], SEEK_SET) = 0 +read(, "\xf8\x00\x07\x01\x41\x00\x5f\x00\x00\x00\x00\x00\xcb\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1c\x83\x2c\x2c\x2a\x80\x22"..., 3811) = 3811 + +_llseek(, 509520, [509520], SEEK_SET) = 0 +read(, "\xbe\xe5\xb6\x09\x0c\x0f\x00\x00", 8) = 8 -_llseek(34, 623832, [623832], SEEK_SET) = 0 -read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20 ```
- Notable is that we still load 5 type 2 records, even though there's just a - single librarian, and 8 compass points. Why 5? +Notable is that we still load 5 type 2 records, even though there's just a +single librarian, and 8 compass points. Why 5? + +Also notable is that we read from `idx` **before** we read from `ani`. + +Other data may be loaded at program start, of course. What other files seem +relevant? + +* `Data/AniObDef.dat` +* `Data/HasAction.dat` +* `Data/VehicDef.dat` +* `Data/WeapDef.dat` + +These all reference animations in one way or another. + +## `HasAction.dat` + +This file seems relevant as it says whether or not particular animations exist +for the different types of character, which maps directly to what is stored in +the .ani file - and so must affect lookups thereof. + +Fortunately, it's commented extensively. For each "Character Type", there are +36 different possible animations. + +Here's a table representation of the data: + +``` + Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult +00 x x x x x x x x x x x x x x x x x x x x x x x +01 x x x x x x x x x x x x x x x x x x x x x x x +02 x x x x x x x x x x x x x x x x x x x x x x x +03 +04 +05 +06 x x x x x x x x x x x x x x x x x x x x +07 x x x x x x x x x x x x x x x x x x x x x x x +08 x x x x x x x x x x x x x x x x +09 +10 +11 +12 +13 +14 x x x x x x x x x x x x x +15 x x x x x x x x x x x x x +16 x x x x x x x x x x x x x +17 x x x x x x x x x x x x x +18 x x x x x x x x x x x x x +19 x x x x x x x x x x x x x +20 x x x x x x x x x x x x x +21 x x x x x x x x x x x x x +22 x x x x x x x x x x x x x x +23 x x x x x x x x x x x x x +24 x x x x x x x x x x x x x x x x +25 x x x x x x x x x x x x x x x x x x x x x x +26 x x x x x x x x x x x x x x x x x +27 x x x x x x x x x x x x x x x x x x x x x +28 x x x x x x x x x x x x x x +29 x x x +30 x +31 x +32 x x x +33 x x x x x x x x x x x x x x x x x x x x x +34 x x x x x x x x x x x x x x x x x x x x x x x +35 x x x x x x x +``` + +`WarHammer.ani` doesn't have blank sprites for the unchecked cells, so this must +surely be used to map between set-of-sprites and `AnimAction`. + +I think we still need the data in `.idx` for a full picture, though. Things we +still need: + +* Mapping of character type to sprite directory index in `WarHammer.ani` +* Number of frames in each AnimAction + +Either of these could be hardcoded, or dynamic. diff --git a/doc/formats/index.md b/doc/formats/index.md index a54a0d9..53fd865 100644 --- a/doc/formats/index.md +++ b/doc/formats/index.md @@ -17,14 +17,15 @@ remake. * `*.dat` # plaintext files defining properties of objects. No single format * **PARSED** * `Accounting.dat` # key = value => internal/data/accounting.go - * [`AniObjDef.dat`](ani.mnu) # animated object definitions + * `AniObjDef.dat` # animated object definitions * `GenericData.dat` # Generic Game Settings + * [`HasAction.dat`](ani.md) # "Are there animation for each of the character" - list of booleans * **TODO** * `ChaNames.dat` # list of character names * `Coordinates.dat` # Weapon Firing Coordinates * `Defs.dat` # defines properties for objects and tiles, each of which seems to have an id * `GDestroy.dat` # table of what destroys what? - * `HasAction.dat` # "Are there animation for each of the character" - list of booleans + * `MiniMap.dat` # lots of seemingly random numbers. IDs? * `MissionBriefing.dat` # Contains all Campaign Mission Briefing Text. Sections: "CAMPAIGN MISSION X ... END CAMPAIGN MISSION X" * `PWeight.dat` # Personality weights for the individual character types @@ -75,8 +76,9 @@ remake. * [`MultiMaps/`](maps.md#multimaps) * [`*.MAP`](maps.md) * [`*.TXT`](maps.md) -* [✓] [`Obj/`](obj.md) - * [`*.obj`](obj.md) +* [`Obj/`](obj.md) + * [ ] `cpiece.rec` # "Rects for various cursor piece types..." + * [✓] [`*.obj`](obj.md) * [✓] `Pic/` * `*.pcx` # Standard .pcx format * `RandomMaps/` diff --git a/go.mod b/go.mod index fcbbe34..0d9f7b2 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/BurntSushi/toml v0.3.1 + github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5 github.com/jfreymuth/oggvorbis v1.0.1 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 052b599..08ad247 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 h1:7QVNyw2v9R1qOvbe9vfeVJWWKCSnd2Ap+8l8/CtG9LM= +github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065/go.mod h1:uN4GbWHfit2ByfOKQ4K6fuLy1/Os2eLynsIrDvjiDgM= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= diff --git a/internal/data/has_action.go b/internal/data/has_action.go new file mode 100644 index 0000000..ecd9553 --- /dev/null +++ b/internal/data/has_action.go @@ -0,0 +1,178 @@ +package data + +import ( + "fmt" + + "github.com/emef/bitfield" + + "code.ur.gs/lupine/ordoor/internal/util/asciiscan" +) + +// AnimAction represents an animation that is stored in WarHammer.ani +type AnimAction int + +// CharacterType represents one of the different types of character in the game. +// +// TODO: can we load the list of character types anywhere or is it hardcoded in +// the original too? +type CharacterType int + +const ( + AnimActionNone AnimAction = 0 + AnimActionAnim AnimAction = 1 + AnimActionWalk AnimAction = 2 + AnimActionExplosion AnimAction = 3 + AnimActionProjectile AnimAction = 4 + AnimActionSmoke AnimAction = 5 + AnimActionStandingShoot AnimAction = 6 + AnimActionStandingDeath AnimAction = 7 + AnimActionPain AnimAction = 8 + AnimActionSpellFx1 AnimAction = 9 + AnimActionSpellFx2 AnimAction = 10 + AnimActionSpellFx3 AnimAction = 11 + AnimActionSpellFx4 AnimAction = 12 + AnimActionSpellFx5 AnimAction = 13 + AnimActionRun AnimAction = 14 + AnimActionCrouch AnimAction = 15 + AnimActionStand AnimAction = 16 + AnimActionStandingRead AnimAction = 17 + AnimActionStandingUnready AnimAction = 18 + AnimActionCrouchingReady AnimAction = 19 + AnimActionCrouchingUnready AnimAction = 20 + AnimActionCrouchingShoot AnimAction = 21 + AnimActionStandingGrenade AnimAction = 22 + AnimActionCrouchingGrenade AnimAction = 23 + AnimActionDrawMelee AnimAction = 24 + AnimActionSlash AnimAction = 25 + AnimActionStab AnimAction = 26 + AnimActionBlown AnimAction = 27 + AnimActionCrouchingDeath AnimAction = 28 + AnimActionJump AnimAction = 29 + AnimActionHeal AnimAction = 30 + AnimActionTechWork AnimAction = 31 + AnimActionCast AnimAction = 32 + AnimActionShoot AnimAction = 33 + AnimActionDeath AnimAction = 34 + AnimActionFromWarp AnimAction = 35 + + AnimActionStart = AnimActionNone + AnimActionEnd = AnimActionFromWarp + AnimActionCount = AnimActionEnd - AnimActionStart + 1 + + // FIXME: indexed from 1, very annoying + CharacterTypeTactical CharacterType = 1 + CharacterTypeAssault CharacterType = 2 + CharacterTypeDevastator CharacterType = 3 + CharacterTypeTerminator CharacterType = 4 + CharacterTypeApothecary CharacterType = 5 + CharacterTypeTechmarine CharacterType = 6 + CharacterTypeChaplain CharacterType = 7 + CharacterTypeLibrarian CharacterType = 8 + CharacterTypeCaptain CharacterType = 9 + CharacterTypeChaosMarine CharacterType = 10 + CharacterTypeChaosLord CharacterType = 11 + CharacterTypeChaosChaplain CharacterType = 12 + CharacterTypeChaosSorcerer CharacterType = 13 + CharacterTypeChaosTerminator CharacterType = 14 + CharacterTypeKhorneBerserker CharacterType = 15 + CharacterTypeBloodThirster CharacterType = 16 + CharacterTypeBloodLetter CharacterType = 17 + CharacterTypeFleshHound CharacterType = 18 + CharacterTypeLordOfChange CharacterType = 19 + CharacterTypeFlamer CharacterType = 20 + CharacterTypePinkHorror CharacterType = 21 + CharacterTypeBlueHorror CharacterType = 22 + CharacterTypeChaosCultist CharacterType = 23 + + CharacterTypeStart = CharacterTypeTactical + CharacterTypeEnd = CharacterTypeChaosCultist + CharacterTypeCount = CharacterTypeEnd - CharacterTypeStart + 1 +) + +// HasAction tells us whether a character has an animation or not. +type HasAction struct { + bits bitfield.BitField +} + +func LoadHasAction(filename string) (*HasAction, error) { + scanner, err := asciiscan.New(filename) + if err != nil { + return nil, err + } + + defer scanner.Close() + + out := &HasAction{ + bits: bitfield.New(int(CharacterTypeCount) * int(AnimActionCount)), + } + + // Reuse this for every loop + var actions [AnimActionCount]bool + ptrs := make([]*bool, len(actions)) + for i, _ := range actions { + ptrs[i] = &actions[i] + } + + for c := CharacterTypeStart; c <= CharacterTypeEnd; c++ { + if err := scanner.ConsumeBoolPtrs(ptrs...); err != nil { + return nil, err + } + + for j, value := range actions { + a := AnimActionStart + AnimAction(j) + + out.set(c, a, value) + } + } + + return out, nil +} + +func (h *HasAction) Check(c CharacterType, a AnimAction) bool { + return h.bits.Test(h.offset(c, a)) +} + +func (h *HasAction) offset(c CharacterType, a AnimAction) uint32 { + // Best to view this as a 2D array with CharacterTypeCount * AnimActionCount elements + i := uint32(c - CharacterTypeStart) + j := uint32(a - AnimActionStart) + + return (i * uint32(AnimActionCount)) + j +} + +func (h *HasAction) set(c CharacterType, a AnimAction, value bool) { + if value { + h.bits.Set(h.offset(c, a)) + } else { + h.bits.Clear(h.offset(c, a)) + } +} + +// Actions returns the list of animations that a character type has +func (h *HasAction) Actions(c CharacterType) []AnimAction { + var out []AnimAction + + for j := AnimActionStart; j < AnimActionCount; j++ { + if h.Check(c, j) { + out = append(out, j) + } + } + + return out +} + +func (h *HasAction) Print() { + fmt.Println(" Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult") + for a := AnimActionStart; a <= AnimActionEnd; a++ { + fmt.Printf("%.2d", int(a)) + for c := CharacterTypeStart; c <= CharacterTypeEnd; c++ { + if h.Check(c, a) { + fmt.Print(" x ") + } else { + fmt.Print(" ") + } + } + + fmt.Println("") + } +} diff --git a/internal/idx/idx.go b/internal/idx/idx.go new file mode 100644 index 0000000..e9405dd --- /dev/null +++ b/internal/idx/idx.go @@ -0,0 +1,73 @@ +// package idx parses the Idx/WarHammer.idx file. +// +// No, I don't know what it's for yet. +package idx + +import ( + "encoding/binary" + //"fmt" + "log" + "os" + //"strings" +) + +type Type1Record struct { + Offset1 uint32 // Where the type2 for this type1 is to be found + Unknown1 uint32 // ??? + Offset2 uint32 // Another offset? But what to? +} + +type Type2Record struct { + Unknown1 [4]byte // ??? + Offset uint32 // Where the type3 for this type2 is to be found + Unknown2 [4]byte // ?? +} + +type Type3Record struct { + First20 [20]byte + Last78 [78]byte +} + +type Idx struct { + Filename string + + Type1Records [512]Type1Record // Experimentally, there seem to be 512 of these + Type2Records []Type2Record + Type3Records []Type3Record +} + +func Load(filename string) (*Idx, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + defer f.Close() + + out := &Idx{Filename: filename} + + if err := binary.Read(f, binary.LittleEndian, &out.Type1Records); err != nil { + return nil, err + } + + for i, rec := range out.Type1Records { + var off1diff uint32 + var off2diff uint32 + + if i > 0 && rec.Offset1 > 0 { + lastRec := out.Type1Records[i-1] + off1diff = rec.Offset1 - lastRec.Offset1 + off2diff = rec.Offset2 - lastRec.Offset2 + } + + log.Printf( + "%.3d: 0x%.6x diff=%.6d %.4d 0x%.6x diff=%.6d", + i, rec.Offset1, off1diff, rec.Unknown1, rec.Offset2, off2diff, + // i, + // rec.Unknown1[0], rec.Unknown1[1], rec.Unknown1[2], rec.Unknown1[3], + // rec.Unknown1[4], rec.Unknown1[5], rec.Unknown1[6], rec.Unknown1[7], + ) + } + + return out, nil +} diff --git a/internal/menus/menus.go b/internal/menus/menus.go index dc0c99d..53a9e2a 100644 --- a/internal/menus/menus.go +++ b/internal/menus/menus.go @@ -201,12 +201,18 @@ func loadProperties(menu *Menu, scanner *asciiscan.Scanner) error { return err } + // DeBrief.mnu misspells these + parts := strings.SplitN(strings.ToUpper(k), " ", 3) + if len(parts) > 2 { + k = strings.Join(parts[0:2], " ") + } + switch strings.ToUpper(k) { - case "BACKGROUND COLOR 0..255..-1 TRANS": + case "BACKGROUND COLOR": menu.BackgroundColor = data.ColorPalette[vInt] - case "HYPERTEXT COLOR 0..255": + case "HYPERTEXT COLOR": menu.HypertextColor = data.ColorPalette[vInt] - case "FONT TYPE 0..5": + case "FONT TYPE": menu.FontType = vInt default: return fmt.Errorf("Unhandled menu property in %v: %q=%q", menu.Name, k, v) diff --git a/internal/util/asciiscan/asciiscan.go b/internal/util/asciiscan/asciiscan.go index e6b4afb..7687315 100644 --- a/internal/util/asciiscan/asciiscan.go +++ b/internal/util/asciiscan/asciiscan.go @@ -123,6 +123,15 @@ func (s *Scanner) ConsumeInt() (int, error) { return strconv.Atoi(str) } +func (s *Scanner) ConsumeBool() (bool, error) { + integer, err := s.ConsumeInt() + if err != nil { + return false, err + } + + return (integer > 0), nil +} + // Reads a list of non-property lines, skipping any that match the given strings func (s *Scanner) ConsumeStringList(skip ...string) ([]string, error) { skipper := make(map[string]bool, len(skip)) @@ -166,6 +175,16 @@ func (s *Scanner) ConsumeIntPtr(to *int) error { return nil } +func (s *Scanner) ConsumeBoolPtr(to *bool) error { + val, err := s.ConsumeBool() + if err != nil { + return err + } + + *to = val + return nil +} + func (s *Scanner) ConsumeIntPtrs(ptrs ...*int) error { for _, ptr := range ptrs { if err := s.ConsumeIntPtr(ptr); err != nil { @@ -175,3 +194,13 @@ func (s *Scanner) ConsumeIntPtrs(ptrs ...*int) error { return nil } + +func (s *Scanner) ConsumeBoolPtrs(ptrs ...*bool) error { + for _, ptr := range ptrs { + if err := s.ConsumeBoolPtr(ptr); err != nil { + return err + } + } + + return nil +}