6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level */
8 /* ------------------------------------------------------------------------- */
10 void setLevelInfoToDefaults_SP()
12 LevelInfoType *header = &native_sp_level.header;
13 char *empty_title = "-------- EMPTY --------";
16 native_sp_level.width = SP_PLAYFIELD_WIDTH;
17 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
19 for (x = 0; x < native_sp_level.width; x++)
20 for (y = 0; y < native_sp_level.height; y++)
21 native_sp_level.playfield[x][y] = fiSpace;
23 /* copy string (without terminating '\0' character!) */
24 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
25 header->LevelTitle[i] = empty_title[i];
27 header->InitialGravity = 0;
29 header->InitialFreezeZonks = 0;
30 header->InfotronsNeeded = 0;
31 header->SpecialPortCount = 0;
32 header->SpeedByte = 0;
33 header->CheckSumByte = 0;
34 header->DemoRandomSeed = 0;
36 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
38 SpecialPortType *port = &header->SpecialPort[i];
40 port->PortLocation = 0;
42 port->FreezeZonks = 0;
43 port->FreezeEnemies = 0;
46 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
47 for (i = 0; i < SP_HEADER_SIZE; i++)
48 native_sp_level.header_raw_bytes[i] = 0x20;
50 native_sp_level.demo.is_available = FALSE;
51 native_sp_level.demo.length = 0;
54 void copyInternalEngineVars_SP()
56 char *preceding_playfield_memory[] =
58 "95 89 95 89 95 89 3b 8a 3b 8a 3b 8a 3b 8a 3b 8a", // |......;.;.;.;.;.|
59 "3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a e8 8a e8 8a", // |;.;.;.è.è.è.è.è.|
60 "e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b b1 8b b1 8b", // |è.è.è.±.±.±.±.±.|
61 "b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c 85 8c 85 8c", // |±.±.±...........|
62 "85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d 5b 8d 5b 8d", // |......[.[.[.[.[.|
63 "5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e 06 8e 06 8e", // |[.[.[...........|
64 "06 8e 06 8e 06 8e ac 8e ac 8e ac 8e ac 8e ac 8e", // |......¬.¬.¬.¬.¬.|
65 "ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f 59 8f 59 8f", // |¬.¬.¬.Y.Y.Y.Y.Y.|
66 "59 8f 59 8f 59 8f 00 00 70 13 00 00 00 00 e8 17", // |Y.Y.Y...p.....è.|
67 "00 00 00 00 00 00 69 38 00 00 00 00 00 00 00 00", // |......i8........|
68 "00 00 00 00 00 00 00 00 d0 86 00 00 b2 34 00 00", // |........Ð...²4..|
69 "00 00 00 00 00 00 8f 8b 1d 34 00 00 00 00 00 00", // |.........4......|
70 "00 00 00 00 23 39 09 09 00 0c 00 08 00 58 00 00", // |....#9.......X..|
71 "00 00 00 25 77 06 7f 00 00 00 01 00 00 00 00 00", // |...%w...........|
72 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
73 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
74 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
75 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
76 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
77 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
78 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
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 ec 06 26 05 00 00 00", // |.........ì.&....|
81 "00 00 00 01 00 00 00 00 31 32 33 34 35 36 37 38", // |........12345678|
82 "39 30 2d 00 08 00 51 57 45 52 54 59 55 49 4f 50", // |90-...QWERTYUIOP|
83 "00 00 0a 00 41 53 44 46 47 48 4a 4b 4c 00 00 00", // |....ASDFGHJKL...|
84 "00 00 5a 58 43 56 42 4e 4d 00 00 00 00 00 00 20", // |..ZXCVBNM...... |
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 00 00 00 00 00 00 00", // |................|
88 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
89 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
90 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
91 "00 00 00 00 00 00 2e 00 1e 00 31 00 14 00 39 00", // |..........1...9.|
92 "1f 00 14 00 18 00 ff ff 01 00 01 4c 45 56 45 4c", // |......ÿÿ...LEVEL|
93 "53 2e 44 41 54 00 00 00 00 00 00 00 00 00 00 00", // |S.DAT...........|
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 00 00 00 00 00 00 00 00 00 00", // |................|
99 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
100 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
104 int preceding_buffer_size = 0;
109 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
110 preceding_buffer_size += 8; /* eight 16-bit integer values */
113 LInfo = native_sp_level.header;
115 FieldWidth = native_sp_level.width;
116 FieldHeight = native_sp_level.height;
119 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
120 LevelMax = (FieldWidth * FieldHeight) - 1;
122 FileMax = FieldMax + native_sp_level.demo.length;
124 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
125 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
127 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
129 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
135 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
136 for (i = -FieldWidth; i < 0; i++)
137 PlayField16[i] = 0x20;
142 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
144 char *s = preceding_playfield_memory[i];
145 boolean hi_byte = FALSE; /* little endian data => start with low byte */
147 while (s[0] != '\0' && s[1] != '\0')
149 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
150 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
151 int byte = (hi_nibble << 4) | lo_nibble;
156 PlayField16[-preceding_buffer_size + count] |= byte;
172 for (y = 0; y < native_sp_level.height; y++)
173 for (x = 0; x < native_sp_level.width; x++)
174 PlayField8[count++] = native_sp_level.playfield[x][y];
176 /* add raw header bytes to subsequent playfield buffer zone */
177 for (i = 0; i < SP_HEADER_SIZE; i++)
178 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
180 for (i = 0; i < count; i++)
182 PlayField16[i] = PlayField8[i];
183 DisPlayField[i] = PlayField8[i];
194 printf("----------\n");
195 for (i = 0; i < preceding_buffer_size + FieldMax; i++)
197 int x = PlayField16[-preceding_buffer_size + i];
199 printf("%c%c", x & 0xff, x >> 8);
201 printf("----------\n");
211 for (i = 0; y = 0; y < native_sp_level.height; y++)
213 for (x = 0; x < native_sp_level.width; x++)
215 PlayField8[i] = native_sp_level.playfield[x][y];
217 PlayField16[i] = PlayField8[i];
218 DisPlayField[i] = PlayField8[i];
227 if (native_sp_level.demo.is_available)
229 DemoAvailable = True;
231 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
233 for (i = 0; i < native_sp_level.demo.length; i++)
234 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
237 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
238 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
239 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
241 DemoPointer = FieldMax + 1;
242 DemoOffset = DemoPointer;
243 DemoKeyRepeatCounter = 0;
245 GravityFlag = LInfo.InitialGravity;
246 FreezeZonks = LInfo.InitialFreezeZonks;
249 /* this is set by main game tape code to native random generator directly */
253 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
256 RandomSeed = LInfo.DemoRandomSeed;
263 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
264 boolean demo_available)
266 LevelInfoType *header = &native_sp_level.header;
269 /* for details of the Supaplex level format, see Herman Perk's Supaplex
270 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
272 native_sp_level.width = width;
273 native_sp_level.height = height;
275 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
276 for (y = 0; y < native_sp_level.height; y++)
277 for (x = 0; x < native_sp_level.width; x++)
278 native_sp_level.playfield[x][y] = getFile8Bit(file);
280 /* read level header (96 bytes) */
282 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
284 /* initial gravity: 1 == "on", anything else (0) == "off" */
285 header->InitialGravity = getFile8Bit(file);
287 /* SpeedFixVersion XOR 0x20 */
288 header->Version = getFile8Bit(file);
290 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
291 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
292 header->LevelTitle[i] = getFile8Bit(file);
294 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
295 header->InitialFreezeZonks = getFile8Bit(file);
297 /* number of infotrons needed; 0 means that Supaplex will count the total
298 amount of infotrons in the level and use the low byte of that number
299 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
300 header->InfotronsNeeded = getFile8Bit(file);
302 /* number of special ("gravity") port entries below (maximum 10 allowed) */
303 header->SpecialPortCount = getFile8Bit(file);
306 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
309 /* database of properties of up to 10 special ports (6 bytes per port) */
310 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
312 SpecialPortType *port = &header->SpecialPort[i];
314 /* high and low byte of the location of a special port; if (x, y) are the
315 coordinates of a port in the field and (0, 0) is the top-left corner,
316 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
317 of what may be expected: Supaplex works with a game field in memory
318 which is 2 bytes per tile) */
319 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
323 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
324 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
326 printf("::: %d: port_location == %d => (%d, %d)\n",
327 i, port->PortLocation, port_x, port_y);
331 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
332 port->Gravity = getFile8Bit(file);
334 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
335 port->FreezeZonks = getFile8Bit(file);
337 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
338 port->FreezeEnemies = getFile8Bit(file);
340 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
343 /* SpeedByte XOR Highbyte(RandomSeed) */
344 header->SpeedByte = getFile8Bit(file);
346 /* CheckSum XOR SpeedByte */
347 header->CheckSumByte = getFile8Bit(file);
349 /* random seed used for recorded demos */
350 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
351 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
354 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
357 /* auto-determine number of infotrons if it was stored as "0" -- see above */
358 if (header->InfotronsNeeded == 0)
360 for (x = 0; x < native_sp_level.width; x++)
361 for (y = 0; y < native_sp_level.height; y++)
362 if (native_sp_level.playfield[x][y] == fiInfotron)
363 header->InfotronsNeeded++;
365 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
368 /* read raw level header bytes (96 bytes) */
370 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
371 for (i = 0; i < SP_HEADER_SIZE; i++)
372 native_sp_level.header_raw_bytes[i] = fgetc(file);
374 /* also load demo tape, if available (only in single level files) */
378 int level_nr = getFile8Bit(file);
380 level_nr &= 0x7f; /* clear highest bit */
381 level_nr = (level_nr < 1 ? 1 :
382 level_nr > 111 ? 111 : level_nr);
384 native_sp_level.demo.level_nr = level_nr;
386 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
388 native_sp_level.demo.data[i] = getFile8Bit(file);
390 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
398 native_sp_level.demo.length = i;
399 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
403 boolean LoadNativeLevel_SP(char *filename, int level_pos)
407 char name_first, name_last;
408 struct LevelInfo_SP multipart_level;
409 int multipart_xpos, multipart_ypos;
410 boolean is_multipart_level;
411 boolean is_first_part;
412 boolean reading_multipart_level = FALSE;
413 boolean use_empty_level = FALSE;
414 LevelInfoType *header = &native_sp_level.header;
415 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
416 strSuffixLower(filename, ".mpx"));
417 boolean demo_available = is_single_level_file;
418 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
419 int file_seek_pos = level_pos * SP_LEVEL_SIZE;
420 int level_width = SP_PLAYFIELD_WIDTH;
421 int level_height = SP_PLAYFIELD_HEIGHT;
423 /* always start with reliable default values */
424 setLevelInfoToDefaults_SP();
425 copyInternalEngineVars_SP();
427 if (!(file = fopen(filename, MODE_READ)))
429 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
436 char mpx_chunk_name[4 + 1];
439 LevelDescriptor *mpx_level_desc;
441 getFileChunkBE(file, mpx_chunk_name, NULL);
443 if (!strEqual(mpx_chunk_name, "MPX "))
445 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
451 mpx_version = getFile16BitLE(file);
453 if (mpx_version != 1)
455 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
461 mpx_level_count = getFile16BitLE(file);
463 if (mpx_level_count < 1)
465 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
471 if (level_pos >= mpx_level_count)
473 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
479 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
481 for (i = 0; i < mpx_level_count; i++)
483 LevelDescriptor *ldesc = &mpx_level_desc[i];
485 ldesc->Width = getFile16BitLE(file);
486 ldesc->Height = getFile16BitLE(file);
487 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
488 ldesc->Size = getFile32BitLE(file);
491 level_width = mpx_level_desc[level_pos].Width;
492 level_height = mpx_level_desc[level_pos].Height;
494 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
497 /* position file stream to the requested level (in case of level package) */
498 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
500 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
505 /* there exist Supaplex level package files with multi-part levels which
506 can be detected as follows: instead of leading and trailing dashes ('-')
507 to pad the level name, they have leading and trailing numbers which are
508 the x and y coordinations of the current part of the multi-part level;
509 if there are '?' characters instead of numbers on the left or right side
510 of the level name, the multi-part level consists of only horizontal or
513 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
515 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
518 /* check if this level is a part of a bigger multi-part level */
520 if (is_single_level_file)
523 name_first = header->LevelTitle[0];
524 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
527 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
528 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
531 ((name_first == '?' || name_first == '1') &&
532 (name_last == '?' || name_last == '1'));
534 if (is_multipart_level)
536 /* correct leading multipart level meta information in level name */
538 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
540 header->LevelTitle[i] = '-';
542 /* correct trailing multipart level meta information in level name */
543 for (i = SP_LEVEL_NAME_LEN - 1;
544 i >= 0 && header->LevelTitle[i] == name_last;
546 header->LevelTitle[i] = '-';
549 /* ---------- check for normal single level ---------- */
551 if (!reading_multipart_level && !is_multipart_level)
553 /* the current level is simply a normal single-part level, and we are
554 not reading a multi-part level yet, so return the level as it is */
559 /* ---------- check for empty level (unused multi-part) ---------- */
561 if (!reading_multipart_level && is_multipart_level && !is_first_part)
563 /* this is a part of a multi-part level, but not the first part
564 (and we are not already reading parts of a multi-part level);
565 in this case, use an empty level instead of the single part */
567 use_empty_level = TRUE;
572 /* ---------- check for finished multi-part level ---------- */
574 if (reading_multipart_level &&
575 (!is_multipart_level ||
576 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
579 /* we are already reading parts of a multi-part level, but this level is
580 either not a multi-part level, or a part of a different multi-part
581 level; in both cases, the multi-part level seems to be complete */
586 /* ---------- here we have one part of a multi-part level ---------- */
588 reading_multipart_level = TRUE;
590 if (is_first_part) /* start with first part of new multi-part level */
592 /* copy level info structure from first part */
593 multipart_level = native_sp_level;
595 /* clear playfield of new multi-part level */
596 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
597 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
598 multipart_level.playfield[x][y] = fiSpace;
601 if (name_first == '?')
603 if (name_last == '?')
606 multipart_xpos = (int)(name_first - '0');
607 multipart_ypos = (int)(name_last - '0');
610 printf("----------> part (%d/%d) of multi-part level '%s'\n",
611 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
614 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
615 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
617 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
622 multipart_level.width = MAX(multipart_level.width,
623 multipart_xpos * SP_PLAYFIELD_WIDTH);
624 multipart_level.height = MAX(multipart_level.height,
625 multipart_ypos * SP_PLAYFIELD_HEIGHT);
627 /* copy level part at the right position of multi-part level */
628 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
630 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
632 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
633 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
635 multipart_level.playfield[start_x + x][start_y + y] =
636 native_sp_level.playfield[x][y];
645 setLevelInfoToDefaults_SP();
647 Error(ERR_WARN, "single part of multi-part level -- using empty level");
650 if (reading_multipart_level)
651 native_sp_level = multipart_level;
653 copyInternalEngineVars_SP();