Преглед на файлове

Script parser can read level scripts

Thomas Buck преди 9 години
родител
ревизия
200377a871
променени са 4 файла, в които са добавени 240 реда и са изтрити 46 реда
  1. 3
    0
      ChangeLog.md
  2. 78
    6
      include/Script.h
  3. 98
    27
      src/Script.cpp
  4. 61
    13
      test/Script.cpp

+ 3
- 0
ChangeLog.md Целия файл

@@ -2,6 +2,9 @@
2 2
 
3 3
 ## OpenRaider (0.1.3) xythobuz <xythobuz@xythobuz.de>
4 4
 
5
+    [ 20140807 ]
6
+    * Script parser successfully loading level scripts
7
+
5 8
     [ 20140806 ]
6 9
     * Improved Script reader and its Unit Test
7 10
     * Successfully parsing the TR2 Script strings!

+ 78
- 6
include/Script.h Целия файл

@@ -11,6 +11,7 @@
11 11
 #include "utils/binary.h"
12 12
 
13 13
 #include <cstdint>
14
+#include <functional>
14 15
 #include <string>
15 16
 #include <vector>
16 17
 
@@ -28,6 +29,66 @@ public:
28 29
         S_Japanese = 4
29 30
     } ScriptLanguage;
30 31
 
32
+    typedef enum {
33
+        OP_PICTURE        = 0,  //!< Unused in TR2. Or PSX?
34
+        OP_PSX_TRACK      = 1,  //!< Does not compile. PSX?
35
+        OP_PSX_FMV        = 2,  //!< Does not compile. PSX?
36
+        OP_FMV            = 3,  //!< Display FMV
37
+        OP_GAME           = 4,  //!< Start a playable level
38
+        OP_CUT            = 5,  //!< Display a cutscene
39
+        OP_COMPLETE       = 6,  //!< Display level-completion stats
40
+        OP_DEMO           = 7,  //!< Display demo sequence
41
+        OP_PSX_DEMO       = 8,  //!< Does not compile. PSX?
42
+        OP_END            = 9,  //!< Closes script sequence
43
+        OP_TRACK          = 10, //!< Play soundtrack (precedes level opcode)
44
+        OP_SUNSET         = 11, //!< Unknown, nothing changes in TR2.
45
+        OP_LOAD_PIC       = 12, //!< Does not compile. PSX or TR3?
46
+        OP_DEADLY_WATER   = 13, //!< Unknown, nothing changes in TR2.
47
+        OP_REMOVE_WEAPONS = 14, //!< Start level without weapons
48
+        OP_GAMECOMPLETE   = 15, //!< End of game. Show stats, start credits sequence, music ID 52.
49
+        OP_CUTANGLE       = 16, //!< Match N-S orientation of Room and animated characters.
50
+        OP_NOFLOOR        = 17, //!< Lara dies when her feet reach given depth.
51
+        OP_STARTINV       = 18, //!< Items given to Lara at level start (+1000), or at all secrets found (+0)
52
+        OP_STARTANIM      = 19, //!< Special animation of Lara when level starts
53
+        OP_SECRETS        = 20, //!< If zero, level does not account for secrets
54
+        OP_KILLTOCOMPLETE = 21, //!< Kill all enemies to finish the level
55
+        OP_REMOVE_AMMO    = 22, //!< Lara starts level without ammo or medi packs
56
+        OP_UNKNOWN        = 23
57
+    } ScriptOpCode;
58
+
59
+    // Items for all-secrets-found go from 0 to 26,
60
+    // for start-inventory add 1000, so 1000 - 1026
61
+    typedef enum {
62
+        OP_WEAPON_PISTOLS     = 0,  //!< Add standard pistols (2)
63
+        OP_WEAPON_SHOTGUN     = 1,  //!< Add shotgun (1)
64
+        OP_WEAPON_AUTOPISTOLS = 2,  //!< Add automatic pistols (2)
65
+        OP_WEAPON_UZIS        = 3,  //!< Add uzis (2)
66
+        OP_WEAPON_HARPOON     = 4,  //!< Add harpoon gun (1)
67
+        OP_WEAPON_M16         = 5,  //!< Add M16 (1)
68
+        OP_WEAPON_ROCKET      = 6,  //!< Add grenade launcher (1)
69
+        OP_AMMO_PISTOLS       = 7,  //!< No effect, infinite ammo
70
+        OP_AMMO_SHOTGUN       = 8,  //!< Add 2 shells
71
+        OP_AMMO_AUTOPISTOLS   = 9,  //!< Add 2 shells
72
+        OP_AMMO_UZIS          = 10, //!< Add 2 shells
73
+        OP_AMMO_HARPOON       = 11, //!< Add 2 harpoons
74
+        OP_AMMO_M16           = 12, //!< Add 2 shells
75
+        OP_AMMO_ROCKET        = 13, //!< Add 1 grenade
76
+        OP_ITEM_FLARE         = 14, //!< Add 1 flare
77
+        OP_ITEM_MEDI          = 15, //!< Add 1 small MediPack
78
+        OP_ITEM_BIGMEDI       = 16, //!< Add 1 big MediPack
79
+        OP_ITEM_PICKUP1       = 17, //!< Add Pickup Item 1
80
+        OP_ITEM_PICKUP2       = 18, //!< Add Pickup Item 2
81
+        OP_ITEM_PUZZLE1       = 19, //!< Add Puzzle Item 1
82
+        OP_ITEM_PUZZLE2       = 20, //!< Add Puzzle Item 2
83
+        OP_ITEM_PUZZLE3       = 21, //!< Add Puzzle Item 3
84
+        OP_ITEM_PUZZLE4       = 22, //!< Add Puzzle Item 4
85
+        OP_ITEM_KEY1          = 23, //!< Add Key Item 1
86
+        OP_ITEM_KEY2          = 24, //!< Add Key Item 2
87
+        OP_ITEM_KEY3          = 25, //!< Add Key Item 3
88
+        OP_ITEM_KEY4          = 26, //!< Add Key Item 4
89
+        OP_ITEM_UNKNOWN       = 27
90
+    } ScriptItem;
91
+
31 92
     Script();
32 93
     ~Script();
33 94
 
@@ -56,11 +117,12 @@ public:
56 117
     std::string getPickupString(unsigned int i, unsigned int j);
57 118
     std::string getKeyString(unsigned int i, unsigned int j);
58 119
 
59
-private:
120
+    void registerScriptHandler(ScriptOpCode op, std::function<int (bool, uint16_t)> func);
121
+    int runScript(unsigned int level);
60 122
 
61
-    void readStringPackage(BinaryFile &f, std::vector<std::string> &v, unsigned int n, bool tag, uint16_t off);
123
+private:
62 124
 
63
-    enum ScriptFlags {
125
+    typedef enum {
64 126
         S_DemoVersion            = (1 << 0),  //!< Don't load a MAIN.SFX
65 127
         S_TitleDisabled          = (1 << 1),  //!< If set, game has no title screen
66 128
         S_CheatModeCheckDisabled = (1 << 2),  //!< Disable flare/step/rotate/jump sequence
@@ -73,6 +135,15 @@ private:
73 135
         S_Unknown                = (1 << 9),  //!< Usually set, no known effect
74 136
         S_SelectAnyLevel         = (1 << 10), //!< Level selectable in Title
75 137
         S_EnableCheatCode        = (1 << 11)  //!< No known effect
138
+    } ScriptFlag;
139
+
140
+    void readStringPackage(BinaryFile &f, std::vector<std::string> &v, unsigned int n);
141
+
142
+    const bool opcodeHasOperand[OP_UNKNOWN] {
143
+        true, true, true, true, true, true,
144
+        false, true, true, false, true, false,
145
+        true, false, false, false, true, true,
146
+        true, true, true, false, false
76 147
     };
77 148
 
78 149
     // Header
@@ -100,8 +171,6 @@ private:
100 171
     uint8_t language;
101 172
     uint16_t secretTrack;
102 173
 
103
-    uint16_t numGameStrings;
104
-
105 174
     // Strings
106 175
     std::vector<std::string> levelNames; // numLevels
107 176
     std::vector<std::string> pictureFilenames; // numPictures
@@ -109,12 +178,15 @@ private:
109 178
     std::vector<std::string> fmvFilenames; // numFMVs
110 179
     std::vector<std::string> levelFilenames; // numLevels
111 180
     std::vector<std::string> cutsceneFilenames; // numCutscenes
112
-    std::vector<std::string> script; // numLevels + 1
181
+    std::vector<std::vector<uint16_t>> script; // numLevels + 1
182
+    uint16_t numGameStrings;
113 183
     std::vector<std::string> gameStrings; // numGameStrings, 89
114 184
     std::vector<std::string> pcStrings; // 41
115 185
     std::vector<std::vector<std::string>> puzzles; // 4 * numLevels
116 186
     std::vector<std::vector<std::string>> pickups; // 2 * numLevels
117 187
     std::vector<std::vector<std::string>> keys; // 4 * numLevels
188
+
189
+    std::function<int (bool, uint16_t)> scriptHandlers[OP_UNKNOWN];
118 190
 };
119 191
 
120 192
 #endif

+ 98
- 27
src/Script.cpp Целия файл

@@ -29,7 +29,7 @@ int Script::load(const char *file) {
29 29
     description = desc;
30 30
 
31 31
     uint16_t gameflowSize = f.readU16();
32
-    assert(gameflowSize == 128);
32
+    assertEqual(gameflowSize, 128);
33 33
 
34 34
     firstOption = f.readU32();
35 35
     titleReplace = f.read32();
@@ -55,7 +55,6 @@ int Script::load(const char *file) {
55 55
     f.seek(f.tell() + 32);
56 56
 
57 57
     flags = f.readU16();
58
-    bool tag = (flags & S_UseSecurityTag) != 0;
59 58
 
60 59
     // Filler 3 (6 bytes)
61 60
     f.seek(f.tell() + 6);
@@ -68,50 +67,87 @@ int Script::load(const char *file) {
68 67
     f.seek(f.tell() + 4);
69 68
 
70 69
     // Strings
71
-    readStringPackage(f, levelNames, numLevels, tag, 0);
72
-    readStringPackage(f, pictureFilenames, numPictures, tag, 0);
73
-    readStringPackage(f, titleFilenames, numTitles, tag, 0);
74
-    readStringPackage(f, fmvFilenames, numFMVs, tag, 0);
75
-    readStringPackage(f, levelFilenames, numLevels, tag, 0);
76
-    readStringPackage(f, cutsceneFilenames, numCutscenes, tag, 0);
70
+    readStringPackage(f, levelNames, numLevels);
71
+    readStringPackage(f, pictureFilenames, numPictures);
72
+    readStringPackage(f, titleFilenames, numTitles);
73
+    readStringPackage(f, fmvFilenames, numFMVs);
74
+    readStringPackage(f, levelFilenames, numLevels);
75
+    readStringPackage(f, cutsceneFilenames, numCutscenes);
76
+
77
+    // Script Package
78
+    uint16_t *offset = new uint16_t[numLevels + 1];
79
+    for (unsigned int i = 0; i < (numLevels + 1); i++) {
80
+        offset[i] = f.readU16();
81
+        assertEqual(offset[i] % 2, 0);
82
+    }
77 83
 
78
-    // Offset definitely TR2 specific?!
79
-    readStringPackage(f, script, numLevels + 1, false, 6);
84
+    uint16_t numBytes = f.readU16() + 6; // Offset TR2 specific?!
85
+    assertEqual(numBytes % 2, 0); // 16 bit opcodes and operands
80 86
 
81
-    numGameStrings = f.readU16();
82
-    assert(numGameStrings == 89);
87
+    uint16_t *list = new uint16_t[numBytes / 2];
88
+    for (uint16_t i = 0; i < (numBytes / 2); i++) {
89
+        list[i] = f.readU16();
90
+    }
83 91
 
84
-    readStringPackage(f, gameStrings, numGameStrings, tag, 0);
92
+    for (unsigned int i = 0; i < (numLevels + 1); i++) {
93
+        unsigned int end = offset[i] / 2;
94
+
95
+        bool readingOperand = false;
96
+        while (readingOperand || (list[end] != OP_END)) {
97
+            if (readingOperand) {
98
+                readingOperand = false;
99
+                end++;
100
+            } else {
101
+                if (opcodeHasOperand[list[end]]) {
102
+                    readingOperand = true;
103
+                }
104
+                end++;
105
+            }
106
+        }
107
+        end++;
85 108
 
86
-    readStringPackage(f, pcStrings, 41, tag, 0);
109
+        std::vector<uint16_t> tmp;
110
+        for (unsigned int a = (offset[i] / 2); a < end; a++)
111
+            tmp.push_back(list[a]);
87 112
 
88
-    readStringPackage(f, puzzles[0], numLevels, tag, 0);
89
-    readStringPackage(f, puzzles[1], numLevels, tag, 0);
90
-    readStringPackage(f, puzzles[2], numLevels, tag, 0);
91
-    readStringPackage(f, puzzles[3], numLevels, tag, 0);
113
+        script.push_back(tmp);
114
+    }
92 115
 
93
-    readStringPackage(f, pickups[0], numLevels, tag, 0);
94
-    readStringPackage(f, pickups[1], numLevels, tag, 0);
116
+    delete [] list;
117
+    delete [] offset;
95 118
 
96
-    readStringPackage(f, keys[0], numLevels, tag, 0);
97
-    readStringPackage(f, keys[1], numLevels, tag, 0);
98
-    readStringPackage(f, keys[2], numLevels, tag, 0);
99
-    readStringPackage(f, keys[3], numLevels, tag, 0);
119
+    // Engine expects 89 game strings!
120
+    numGameStrings = f.readU16();
121
+    assertEqual(numGameStrings, 89);
122
+
123
+    // More strings...
124
+    readStringPackage(f, gameStrings, numGameStrings);
125
+    readStringPackage(f, pcStrings, 41);
126
+    readStringPackage(f, puzzles[0], numLevels);
127
+    readStringPackage(f, puzzles[1], numLevels);
128
+    readStringPackage(f, puzzles[2], numLevels);
129
+    readStringPackage(f, puzzles[3], numLevels);
130
+    readStringPackage(f, pickups[0], numLevels);
131
+    readStringPackage(f, pickups[1], numLevels);
132
+    readStringPackage(f, keys[0], numLevels);
133
+    readStringPackage(f, keys[1], numLevels);
134
+    readStringPackage(f, keys[2], numLevels);
135
+    readStringPackage(f, keys[3], numLevels);
100 136
 
101 137
     return 0;
102 138
 }
103 139
 
104
-void Script::readStringPackage(BinaryFile &f, std::vector<std::string> &v, unsigned int n, bool tag, uint16_t off) {
140
+void Script::readStringPackage(BinaryFile &f, std::vector<std::string> &v, unsigned int n) {
105 141
     uint16_t *offset = new uint16_t[n];
106 142
     for (unsigned int i = 0; i < n; i++)
107 143
         offset[i] = f.readU16();
108 144
 
109
-    uint16_t numBytes = f.readU16() + off;
145
+    uint16_t numBytes = f.readU16();
110 146
 
111 147
     char *list = new char[numBytes];
112 148
     for (uint16_t i = 0; i < numBytes; i++) {
113 149
         list[i] = f.read8();
114
-        if (tag) {
150
+        if (flags & S_UseSecurityTag) {
115 151
             list[i] ^= cypherCode;
116 152
         }
117 153
     }
@@ -202,3 +238,38 @@ std::string Script::getKeyString(unsigned int i, unsigned int j) {
202 238
     return keys.at(i).at(j);
203 239
 }
204 240
 
241
+void Script::registerScriptHandler(ScriptOpCode op, std::function<int (bool, uint16_t)> func) {
242
+    assert(op < OP_UNKNOWN);
243
+    scriptHandlers[op] = func;
244
+}
245
+
246
+int Script::runScript(unsigned int level) {
247
+    assert(level < (numLevels + 1));
248
+    std::vector<uint16_t> s = script.at(level);
249
+
250
+    for (unsigned int i = 0; i < s.size(); i++) {
251
+        uint16_t opcode = s.at(i);
252
+        if (opcode >= OP_UNKNOWN) {
253
+            return 1;
254
+        }
255
+
256
+        uint16_t operand = 0;
257
+        if (opcodeHasOperand[opcode]) {
258
+            if ((i + 1) >= s.size())
259
+                return 2; // Can't read operand!
260
+
261
+            operand = s.at(++i);
262
+        }
263
+
264
+        if (scriptHandlers[opcode]) {
265
+            int error = scriptHandlers[opcode](opcodeHasOperand[opcode], operand);
266
+            if (error != 0)
267
+                return error;
268
+        } else {
269
+            return 3;
270
+        }
271
+    }
272
+
273
+    return 0;
274
+}
275
+

+ 61
- 13
test/Script.cpp Целия файл

@@ -33,31 +33,79 @@
33 33
     std::cout << std::endl; \
34 34
 }
35 35
 
36
+#define registerLambda(x, y) { \
37
+    s.registerScriptHandler(x, [](bool hasOperand, uint16_t operand) { \
38
+        std::cout << "\t" << y; \
39
+        if (hasOperand) \
40
+            std::cout << " (" << operand << ")"; \
41
+        std::cout << std::endl; \
42
+        return 0; \
43
+    }); \
44
+}
45
+
36 46
 namespace {
37
-    int test(const char *f) {
47
+    int test(const char *f, bool strings) {
38 48
         Script s;
39 49
         assertEqual(s.load(f), 0);
40 50
 
41
-        printStrings(s.levelCount(), s.getLevelName, "Level Names");
42
-        printStrings(s.levelCount(), s.getLevelFilename, "Level Filenames");
43
-        printStrings(s.cutsceneCount(), s.getCutsceneFilename, "Cutscenes");
44
-        printStrings(s.titleCount(), s.getTitleFilename, "Titles");
45
-        printStrings(s.videoCount(), s.getVideoFilename, "Videos");
46
-        printStrings(s.gameStringCount(), s.getGameString, "Game Strings");
47
-        printStrings(s.pcStringCount(), s.getPCString, "PC Strings");
51
+        if (strings) {
52
+            printStrings(s.levelCount(), s.getLevelName, "Level Names");
53
+            printStrings(s.levelCount(), s.getLevelFilename, "Level Filenames");
54
+            printStrings(s.cutsceneCount(), s.getCutsceneFilename, "Cutscenes");
55
+            printStrings(s.titleCount(), s.getTitleFilename, "Titles");
56
+            printStrings(s.videoCount(), s.getVideoFilename, "Videos");
57
+            printStrings(s.gameStringCount(), s.getGameString, "Game Strings");
58
+            printStrings(s.pcStringCount(), s.getPCString, "PC Strings");
48 59
 
49
-        printStrings2D(4, s.levelCount(), s.getPuzzleString, "Puzzles");
50
-        printStrings2D(2, s.levelCount(), s.getPickupString, "Pickups");
51
-        printStrings2D(4, s.levelCount(), s.getKeyString, "Keys");
60
+            printStrings2D(4, s.levelCount(), s.getPuzzleString, "Puzzles");
61
+            printStrings2D(2, s.levelCount(), s.getPickupString, "Pickups");
62
+            printStrings2D(4, s.levelCount(), s.getKeyString, "Keys");
63
+        } else {
64
+            registerLambda(Script::OP_PICTURE, "Picture");
65
+            registerLambda(Script::OP_PSX_TRACK, "PSX-Track");
66
+            registerLambda(Script::OP_PSX_FMV, "PSX-FMV");
67
+            registerLambda(Script::OP_FMV, "Show FMV");
68
+            registerLambda(Script::OP_GAME, "Load level");
69
+            registerLambda(Script::OP_CUT, "Cutscene");
70
+            registerLambda(Script::OP_COMPLETE, "Level finished");
71
+            registerLambda(Script::OP_DEMO, "Demo sequence");
72
+            registerLambda(Script::OP_PSX_DEMO, "PSX-Demo");
73
+            registerLambda(Script::OP_END, "End of script");
74
+            registerLambda(Script::OP_TRACK, "Sound Track");
75
+            registerLambda(Script::OP_SUNSET, "Sunset");
76
+            registerLambda(Script::OP_LOAD_PIC, "Load picture");
77
+            registerLambda(Script::OP_DEADLY_WATER, "Deadly water");
78
+            registerLambda(Script::OP_REMOVE_WEAPONS, "Remove weapons");
79
+            registerLambda(Script::OP_GAMECOMPLETE, "End of game!");
80
+            registerLambda(Script::OP_CUTANGLE, "Cutscene angle");
81
+            registerLambda(Script::OP_NOFLOOR, "No floor, fall death");
82
+            registerLambda(Script::OP_STARTINV, "Inventory/Bonus");
83
+            registerLambda(Script::OP_STARTANIM, "Start animation");
84
+            registerLambda(Script::OP_SECRETS, "Secrets");
85
+            registerLambda(Script::OP_KILLTOCOMPLETE, "Kill to complete level");
86
+            registerLambda(Script::OP_REMOVE_AMMO, "Remove ammo");
87
+
88
+            for (unsigned int i = 0; i < (s.levelCount() + 1); i++) {
89
+                if (i == 0)
90
+                    std::cout << "Script for Title:" << std::endl;
91
+                else
92
+                    std::cout << "Script for \"" << s.getLevelName(i - 1) << "\" (" << i - 1 << "):" << std::endl;
93
+                int error = s.runScript(i);
94
+                if (error != 0)
95
+                    std::cout << "Returned " << error << "..." << std::endl;
96
+                std::cout << std::endl;
97
+            }
98
+        }
52 99
 
53 100
         return 0;
54 101
     }
55 102
 }
56 103
 
57
-int main() {
104
+int main(int argc, char *argv[]) {
58 105
     char *f = fullPath("~/.OpenRaider/paks/tr2/TOMBPC.DAT", 0);
59
-    int error = test(f);
106
+    int error = test(f, !((argc > 1) && (argv[1][0] == 's')));
60 107
     delete [] f;
108
+
61 109
     return error;
62 110
 }
63 111
 

Loading…
Отказ
Запис