6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level */
8 /* ------------------------------------------------------------------------- */
10 void setTapeInfoToDefaults_SP()
12 native_sp_level.demo.is_available = FALSE;
13 native_sp_level.demo.length = 0;
16 void setLevelInfoToDefaults_SP()
18 LevelInfoType *header = &native_sp_level.header;
19 char *empty_title = "-------- EMPTY --------";
22 native_sp_level.game_sp = &game_sp;
24 native_sp_level.width = SP_STD_PLAYFIELD_WIDTH;
25 native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
27 for (x = 0; x < native_sp_level.width; x++)
28 for (y = 0; y < native_sp_level.height; y++)
29 native_sp_level.playfield[x][y] = fiSpace;
31 /* copy string (without terminating '\0' character!) */
32 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
33 header->LevelTitle[i] = empty_title[i];
35 header->InitialGravity = 0;
37 header->InitialFreezeZonks = 0;
38 header->InfotronsNeeded = 0;
39 header->SpecialPortCount = 0;
40 header->SpeedByte = 0;
41 header->CheckSumByte = 0;
42 header->DemoRandomSeed = 0;
44 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
46 SpecialPortType *port = &header->SpecialPort[i];
48 port->PortLocation = 0;
50 port->FreezeZonks = 0;
51 port->FreezeEnemies = 0;
54 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
55 for (i = 0; i < SP_HEADER_SIZE; i++)
56 native_sp_level.header_raw_bytes[i] = 0x20;
58 setTapeInfoToDefaults_SP();
61 void copyInternalEngineVars_SP()
63 char *preceding_playfield_memory[] =
65 "95 89 95 89 95 89 3b 8a 3b 8a 3b 8a 3b 8a 3b 8a", // |......;.;.;.;.;.|
66 "3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a e8 8a e8 8a", // |;.;.;.è.è.è.è.è.|
67 "e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b b1 8b b1 8b", // |è.è.è.±.±.±.±.±.|
68 "b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c 85 8c 85 8c", // |±.±.±...........|
69 "85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d 5b 8d 5b 8d", // |......[.[.[.[.[.|
70 "5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e 06 8e 06 8e", // |[.[.[...........|
71 "06 8e 06 8e 06 8e ac 8e ac 8e ac 8e ac 8e ac 8e", // |......¬.¬.¬.¬.¬.|
72 "ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f 59 8f 59 8f", // |¬.¬.¬.Y.Y.Y.Y.Y.|
73 "59 8f 59 8f 59 8f 00 00 70 13 00 00 00 00 e8 17", // |Y.Y.Y...p.....è.|
74 "00 00 00 00 00 00 69 38 00 00 00 00 00 00 00 00", // |......i8........|
75 "00 00 00 00 00 00 00 00 d0 86 00 00 b2 34 00 00", // |........Ð...²4..|
76 "00 00 00 00 00 00 8f 8b 1d 34 00 00 00 00 00 00", // |.........4......|
77 "00 00 00 00 23 39 09 09 00 0c 00 08 00 58 00 00", // |....#9.......X..|
78 "00 00 00 25 77 06 7f 00 00 00 01 00 00 00 00 00", // |...%w...........|
79 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
80 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
81 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
82 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
83 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
84 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
85 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
86 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
87 "00 00 00 00 00 00 00 00 00 ec 06 26 05 00 00 00", // |.........ì.&....|
88 "00 00 00 01 00 00 00 00 31 32 33 34 35 36 37 38", // |........12345678|
89 "39 30 2d 00 08 00 51 57 45 52 54 59 55 49 4f 50", // |90-...QWERTYUIOP|
90 "00 00 0a 00 41 53 44 46 47 48 4a 4b 4c 00 00 00", // |....ASDFGHJKL...|
91 "00 00 5a 58 43 56 42 4e 4d 00 00 00 00 00 00 20", // |..ZXCVBNM...... |
92 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
93 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
94 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
95 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
96 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
97 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
98 "00 00 00 00 00 00 2e 00 1e 00 31 00 14 00 39 00", // |..........1...9.|
99 "1f 00 14 00 18 00 ff ff 01 00 01 4c 45 56 45 4c", // |......ÿÿ...LEVEL|
100 "53 2e 44 41 54 00 00 00 00 00 00 00 00 00 00 00", // |S.DAT...........|
101 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
102 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
103 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
104 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
105 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
106 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
107 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
111 int preceding_buffer_size = 0;
115 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
116 preceding_buffer_size += 8; /* eight 16-bit integer values */
118 /* needed for engine snapshots */
119 game_sp.preceding_buffer_size = preceding_buffer_size;
121 LInfo = native_sp_level.header;
123 FieldWidth = native_sp_level.width;
124 FieldHeight = native_sp_level.height;
127 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
128 LevelMax = (FieldWidth * FieldHeight) - 1;
130 FileMax = FieldMax + native_sp_level.demo.length;
132 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
133 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
134 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
137 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
139 char *s = preceding_playfield_memory[i];
140 boolean hi_byte = FALSE; /* little endian data => start with low byte */
142 while (s[0] != '\0' && s[1] != '\0')
144 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
145 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
146 int byte = (hi_nibble << 4) | lo_nibble;
151 PlayField16[-preceding_buffer_size + count] |= byte;
166 for (y = 0; y < native_sp_level.height; y++)
167 for (x = 0; x < native_sp_level.width; x++)
168 PlayField8[count++] = native_sp_level.playfield[x][y];
170 /* add raw header bytes to subsequent playfield buffer zone */
171 for (i = 0; i < SP_HEADER_SIZE; i++)
172 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
174 for (i = 0; i < count; i++)
176 PlayField16[i] = PlayField8[i];
177 DisPlayField[i] = PlayField8[i];
181 if (native_sp_level.demo.is_available)
183 DemoAvailable = True;
185 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
187 for (i = 0; i < native_sp_level.demo.length; i++)
188 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
191 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
192 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
193 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
195 GravityFlag = LInfo.InitialGravity;
196 FreezeZonks = LInfo.InitialFreezeZonks;
199 /* this is set by main game tape code to native random generator directly */
201 RandomSeed = LInfo.DemoRandomSeed;
207 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
208 boolean demo_available)
210 LevelInfoType *header = &native_sp_level.header;
213 /* for details of the Supaplex level format, see Herman Perk's Supaplex
214 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
216 native_sp_level.width = width;
217 native_sp_level.height = height;
219 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
220 for (y = 0; y < native_sp_level.height; y++)
221 for (x = 0; x < native_sp_level.width; x++)
222 native_sp_level.playfield[x][y] = getFile8Bit(file);
224 /* read level header (96 bytes) */
226 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
228 /* initial gravity: 1 == "on", anything else (0) == "off" */
229 header->InitialGravity = getFile8Bit(file);
231 /* SpeedFixVersion XOR 0x20 */
232 header->Version = getFile8Bit(file);
234 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
235 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
236 header->LevelTitle[i] = getFile8Bit(file);
238 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
239 header->InitialFreezeZonks = getFile8Bit(file);
241 /* number of infotrons needed; 0 means that Supaplex will count the total
242 amount of infotrons in the level and use the low byte of that number
243 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
244 header->InfotronsNeeded = getFile8Bit(file);
246 /* number of special ("gravity") port entries below (maximum 10 allowed) */
247 header->SpecialPortCount = getFile8Bit(file);
249 /* database of properties of up to 10 special ports (6 bytes per port) */
250 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
252 SpecialPortType *port = &header->SpecialPort[i];
254 /* high and low byte of the location of a special port; if (x, y) are the
255 coordinates of a port in the field and (0, 0) is the top-left corner,
256 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
257 of what may be expected: Supaplex works with a game field in memory
258 which is 2 bytes per tile) */
259 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
261 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
262 port->Gravity = getFile8Bit(file);
264 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
265 port->FreezeZonks = getFile8Bit(file);
267 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
268 port->FreezeEnemies = getFile8Bit(file);
270 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
273 /* SpeedByte XOR Highbyte(RandomSeed) */
274 header->SpeedByte = getFile8Bit(file);
276 /* CheckSum XOR SpeedByte */
277 header->CheckSumByte = getFile8Bit(file);
279 /* random seed used for recorded demos */
280 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
282 /* auto-determine number of infotrons if it was stored as "0" -- see above */
283 if (header->InfotronsNeeded == 0)
285 for (x = 0; x < native_sp_level.width; x++)
286 for (y = 0; y < native_sp_level.height; y++)
287 if (native_sp_level.playfield[x][y] == fiInfotron)
288 header->InfotronsNeeded++;
290 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
293 /* read raw level header bytes (96 bytes) */
295 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
296 for (i = 0; i < SP_HEADER_SIZE; i++)
297 native_sp_level.header_raw_bytes[i] = fgetc(file);
299 /* also load demo tape, if available (only in single level files) */
303 int level_nr = getFile8Bit(file);
305 level_nr &= 0x7f; /* clear highest bit */
306 level_nr = (level_nr < 1 ? 1 :
307 level_nr > 111 ? 111 : level_nr);
309 native_sp_level.demo.level_nr = level_nr;
311 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
313 native_sp_level.demo.data[i] = getFile8Bit(file);
315 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
323 native_sp_level.demo.length = i;
324 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
328 boolean LoadNativeLevel_SP(char *filename, int level_pos)
332 char name_first, name_last;
333 struct LevelInfo_SP multipart_level;
334 int multipart_xpos, multipart_ypos;
335 boolean is_multipart_level;
336 boolean is_first_part;
337 boolean reading_multipart_level = FALSE;
338 boolean use_empty_level = FALSE;
339 LevelInfoType *header = &native_sp_level.header;
340 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
341 strSuffixLower(filename, ".mpx"));
342 boolean demo_available = is_single_level_file;
343 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
344 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
345 int level_width = SP_STD_PLAYFIELD_WIDTH;
346 int level_height = SP_STD_PLAYFIELD_HEIGHT;
348 /* always start with reliable default values */
349 setLevelInfoToDefaults_SP();
350 copyInternalEngineVars_SP();
352 if (!(file = fopen(filename, MODE_READ)))
354 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
361 char mpx_chunk_name[4 + 1];
364 LevelDescriptor *mpx_level_desc;
366 getFileChunkBE(file, mpx_chunk_name, NULL);
368 if (!strEqual(mpx_chunk_name, "MPX "))
370 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
376 mpx_version = getFile16BitLE(file);
378 if (mpx_version != 1)
380 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
386 mpx_level_count = getFile16BitLE(file);
388 if (mpx_level_count < 1)
390 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
396 if (level_pos >= mpx_level_count)
398 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
404 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
406 for (i = 0; i < mpx_level_count; i++)
408 LevelDescriptor *ldesc = &mpx_level_desc[i];
410 ldesc->Width = getFile16BitLE(file);
411 ldesc->Height = getFile16BitLE(file);
412 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
413 ldesc->Size = getFile32BitLE(file);
416 level_width = mpx_level_desc[level_pos].Width;
417 level_height = mpx_level_desc[level_pos].Height;
419 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
422 /* position file stream to the requested level (in case of level package) */
423 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
425 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
430 /* there exist Supaplex level package files with multi-part levels which
431 can be detected as follows: instead of leading and trailing dashes ('-')
432 to pad the level name, they have leading and trailing numbers which are
433 the x and y coordinations of the current part of the multi-part level;
434 if there are '?' characters instead of numbers on the left or right side
435 of the level name, the multi-part level consists of only horizontal or
438 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
440 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
443 /* check if this level is a part of a bigger multi-part level */
445 if (is_single_level_file)
448 name_first = header->LevelTitle[0];
449 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
452 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
453 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
456 ((name_first == '?' || name_first == '1') &&
457 (name_last == '?' || name_last == '1'));
459 if (is_multipart_level)
461 /* correct leading multipart level meta information in level name */
463 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
465 header->LevelTitle[i] = '-';
467 /* correct trailing multipart level meta information in level name */
468 for (i = SP_LEVEL_NAME_LEN - 1;
469 i >= 0 && header->LevelTitle[i] == name_last;
471 header->LevelTitle[i] = '-';
474 /* ---------- check for normal single level ---------- */
476 if (!reading_multipart_level && !is_multipart_level)
478 /* the current level is simply a normal single-part level, and we are
479 not reading a multi-part level yet, so return the level as it is */
484 /* ---------- check for empty level (unused multi-part) ---------- */
486 if (!reading_multipart_level && is_multipart_level && !is_first_part)
488 /* this is a part of a multi-part level, but not the first part
489 (and we are not already reading parts of a multi-part level);
490 in this case, use an empty level instead of the single part */
492 use_empty_level = TRUE;
497 /* ---------- check for finished multi-part level ---------- */
499 if (reading_multipart_level &&
500 (!is_multipart_level ||
501 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
504 /* we are already reading parts of a multi-part level, but this level is
505 either not a multi-part level, or a part of a different multi-part
506 level; in both cases, the multi-part level seems to be complete */
511 /* ---------- here we have one part of a multi-part level ---------- */
513 reading_multipart_level = TRUE;
515 if (is_first_part) /* start with first part of new multi-part level */
517 /* copy level info structure from first part */
518 multipart_level = native_sp_level;
520 /* clear playfield of new multi-part level */
521 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
522 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
523 multipart_level.playfield[x][y] = fiSpace;
526 if (name_first == '?')
528 if (name_last == '?')
531 multipart_xpos = (int)(name_first - '0');
532 multipart_ypos = (int)(name_last - '0');
535 printf("----------> part (%d/%d) of multi-part level '%s'\n",
536 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
539 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
540 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
542 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
547 multipart_level.width = MAX(multipart_level.width,
548 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
549 multipart_level.height = MAX(multipart_level.height,
550 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
552 /* copy level part at the right position of multi-part level */
553 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
555 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
557 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
558 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
560 multipart_level.playfield[start_x + x][start_y + y] =
561 native_sp_level.playfield[x][y];
570 setLevelInfoToDefaults_SP();
572 Error(ERR_WARN, "single part of multi-part level -- using empty level");
575 if (reading_multipart_level)
576 native_sp_level = multipart_level;
578 copyInternalEngineVars_SP();
583 void SaveNativeLevel_SP(char *filename)
585 LevelInfoType *header = &native_sp_level.header;
589 if (!(file = fopen(filename, MODE_WRITE)))
591 Error(ERR_WARN, "cannot save native level file '%s'", filename);
596 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
597 for (y = 0; y < native_sp_level.height; y++)
598 for (x = 0; x < native_sp_level.width; x++)
599 putFile8Bit(file, native_sp_level.playfield[x][y]);
601 /* write level header (96 bytes) */
603 WriteUnusedBytesToFile(file, 4);
605 putFile8Bit(file, header->InitialGravity);
606 putFile8Bit(file, header->Version);
608 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
609 putFile8Bit(file, header->LevelTitle[i]);
611 putFile8Bit(file, header->InitialFreezeZonks);
612 putFile8Bit(file, header->InfotronsNeeded);
613 putFile8Bit(file, header->SpecialPortCount);
615 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
617 SpecialPortType *port = &header->SpecialPort[i];
619 putFile16BitBE(file, port->PortLocation);
620 putFile8Bit(file, port->Gravity);
621 putFile8Bit(file, port->FreezeZonks);
622 putFile8Bit(file, port->FreezeEnemies);
624 WriteUnusedBytesToFile(file, 1);
627 putFile8Bit(file, header->SpeedByte);
628 putFile8Bit(file, header->CheckSumByte);
629 putFile16BitLE(file, header->DemoRandomSeed);
631 /* also save demo tape, if available */
633 if (native_sp_level.demo.is_available)
635 putFile8Bit(file, native_sp_level.demo.level_nr);
637 for (i = 0; i < native_sp_level.demo.length; i++)
638 putFile8Bit(file, native_sp_level.demo.data[i]);