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_STD_PLAYFIELD_WIDTH;
17 native_sp_level.height = SP_STD_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 /* needed for engine snapshots */
114 game_sp.preceding_buffer_size = preceding_buffer_size;
116 LInfo = native_sp_level.header;
118 FieldWidth = native_sp_level.width;
119 FieldHeight = native_sp_level.height;
122 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
123 LevelMax = (FieldWidth * FieldHeight) - 1;
125 FileMax = FieldMax + native_sp_level.demo.length;
127 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
128 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
130 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
132 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
138 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
139 for (i = -FieldWidth; i < 0; i++)
140 PlayField16[i] = 0x20;
145 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
147 char *s = preceding_playfield_memory[i];
148 boolean hi_byte = FALSE; /* little endian data => start with low byte */
150 while (s[0] != '\0' && s[1] != '\0')
152 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
153 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
154 int byte = (hi_nibble << 4) | lo_nibble;
159 PlayField16[-preceding_buffer_size + count] |= byte;
175 for (y = 0; y < native_sp_level.height; y++)
176 for (x = 0; x < native_sp_level.width; x++)
177 PlayField8[count++] = native_sp_level.playfield[x][y];
179 /* add raw header bytes to subsequent playfield buffer zone */
180 for (i = 0; i < SP_HEADER_SIZE; i++)
181 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
183 for (i = 0; i < count; i++)
185 PlayField16[i] = PlayField8[i];
186 DisPlayField[i] = PlayField8[i];
197 printf("----------\n");
198 for (i = 0; i < preceding_buffer_size + FieldMax; i++)
200 int x = PlayField16[-preceding_buffer_size + i];
202 printf("%c%c", x & 0xff, x >> 8);
204 printf("----------\n");
214 for (i = 0; y = 0; y < native_sp_level.height; y++)
216 for (x = 0; x < native_sp_level.width; x++)
218 PlayField8[i] = native_sp_level.playfield[x][y];
220 PlayField16[i] = PlayField8[i];
221 DisPlayField[i] = PlayField8[i];
230 if (native_sp_level.demo.is_available)
232 DemoAvailable = True;
234 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
236 for (i = 0; i < native_sp_level.demo.length; i++)
237 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
240 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
241 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
242 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
244 DemoPointer = FieldMax + 1;
245 DemoOffset = DemoPointer;
246 DemoKeyRepeatCounter = 0;
248 GravityFlag = LInfo.InitialGravity;
249 FreezeZonks = LInfo.InitialFreezeZonks;
252 /* this is set by main game tape code to native random generator directly */
256 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
259 RandomSeed = LInfo.DemoRandomSeed;
266 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
267 boolean demo_available)
269 LevelInfoType *header = &native_sp_level.header;
272 /* for details of the Supaplex level format, see Herman Perk's Supaplex
273 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
275 native_sp_level.width = width;
276 native_sp_level.height = height;
278 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
279 for (y = 0; y < native_sp_level.height; y++)
280 for (x = 0; x < native_sp_level.width; x++)
281 native_sp_level.playfield[x][y] = getFile8Bit(file);
283 /* read level header (96 bytes) */
285 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
287 /* initial gravity: 1 == "on", anything else (0) == "off" */
288 header->InitialGravity = getFile8Bit(file);
290 /* SpeedFixVersion XOR 0x20 */
291 header->Version = getFile8Bit(file);
293 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
294 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
295 header->LevelTitle[i] = getFile8Bit(file);
297 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
298 header->InitialFreezeZonks = getFile8Bit(file);
300 /* number of infotrons needed; 0 means that Supaplex will count the total
301 amount of infotrons in the level and use the low byte of that number
302 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
303 header->InfotronsNeeded = getFile8Bit(file);
305 /* number of special ("gravity") port entries below (maximum 10 allowed) */
306 header->SpecialPortCount = getFile8Bit(file);
309 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
312 /* database of properties of up to 10 special ports (6 bytes per port) */
313 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
315 SpecialPortType *port = &header->SpecialPort[i];
317 /* high and low byte of the location of a special port; if (x, y) are the
318 coordinates of a port in the field and (0, 0) is the top-left corner,
319 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
320 of what may be expected: Supaplex works with a game field in memory
321 which is 2 bytes per tile) */
322 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
326 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
327 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
329 printf("::: %d: port_location == %d => (%d, %d)\n",
330 i, port->PortLocation, port_x, port_y);
334 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
335 port->Gravity = getFile8Bit(file);
337 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
338 port->FreezeZonks = getFile8Bit(file);
340 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
341 port->FreezeEnemies = getFile8Bit(file);
343 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
346 /* SpeedByte XOR Highbyte(RandomSeed) */
347 header->SpeedByte = getFile8Bit(file);
349 /* CheckSum XOR SpeedByte */
350 header->CheckSumByte = getFile8Bit(file);
352 /* random seed used for recorded demos */
353 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
354 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
357 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
360 /* auto-determine number of infotrons if it was stored as "0" -- see above */
361 if (header->InfotronsNeeded == 0)
363 for (x = 0; x < native_sp_level.width; x++)
364 for (y = 0; y < native_sp_level.height; y++)
365 if (native_sp_level.playfield[x][y] == fiInfotron)
366 header->InfotronsNeeded++;
368 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
371 /* read raw level header bytes (96 bytes) */
373 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
374 for (i = 0; i < SP_HEADER_SIZE; i++)
375 native_sp_level.header_raw_bytes[i] = fgetc(file);
377 /* also load demo tape, if available (only in single level files) */
381 int level_nr = getFile8Bit(file);
383 level_nr &= 0x7f; /* clear highest bit */
384 level_nr = (level_nr < 1 ? 1 :
385 level_nr > 111 ? 111 : level_nr);
387 native_sp_level.demo.level_nr = level_nr;
389 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
391 native_sp_level.demo.data[i] = getFile8Bit(file);
393 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
401 native_sp_level.demo.length = i;
402 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
406 boolean LoadNativeLevel_SP(char *filename, int level_pos)
410 char name_first, name_last;
411 struct LevelInfo_SP multipart_level;
412 int multipart_xpos, multipart_ypos;
413 boolean is_multipart_level;
414 boolean is_first_part;
415 boolean reading_multipart_level = FALSE;
416 boolean use_empty_level = FALSE;
417 LevelInfoType *header = &native_sp_level.header;
418 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
419 strSuffixLower(filename, ".mpx"));
420 boolean demo_available = is_single_level_file;
421 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
422 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
423 int level_width = SP_STD_PLAYFIELD_WIDTH;
424 int level_height = SP_STD_PLAYFIELD_HEIGHT;
426 /* always start with reliable default values */
427 setLevelInfoToDefaults_SP();
428 copyInternalEngineVars_SP();
430 if (!(file = fopen(filename, MODE_READ)))
432 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
439 char mpx_chunk_name[4 + 1];
442 LevelDescriptor *mpx_level_desc;
444 getFileChunkBE(file, mpx_chunk_name, NULL);
446 if (!strEqual(mpx_chunk_name, "MPX "))
448 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
454 mpx_version = getFile16BitLE(file);
456 if (mpx_version != 1)
458 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
464 mpx_level_count = getFile16BitLE(file);
466 if (mpx_level_count < 1)
468 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
474 if (level_pos >= mpx_level_count)
476 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
482 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
484 for (i = 0; i < mpx_level_count; i++)
486 LevelDescriptor *ldesc = &mpx_level_desc[i];
488 ldesc->Width = getFile16BitLE(file);
489 ldesc->Height = getFile16BitLE(file);
490 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
491 ldesc->Size = getFile32BitLE(file);
494 level_width = mpx_level_desc[level_pos].Width;
495 level_height = mpx_level_desc[level_pos].Height;
497 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
500 /* position file stream to the requested level (in case of level package) */
501 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
503 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
508 /* there exist Supaplex level package files with multi-part levels which
509 can be detected as follows: instead of leading and trailing dashes ('-')
510 to pad the level name, they have leading and trailing numbers which are
511 the x and y coordinations of the current part of the multi-part level;
512 if there are '?' characters instead of numbers on the left or right side
513 of the level name, the multi-part level consists of only horizontal or
516 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
518 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
521 /* check if this level is a part of a bigger multi-part level */
523 if (is_single_level_file)
526 name_first = header->LevelTitle[0];
527 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
530 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
531 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
534 ((name_first == '?' || name_first == '1') &&
535 (name_last == '?' || name_last == '1'));
537 if (is_multipart_level)
539 /* correct leading multipart level meta information in level name */
541 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
543 header->LevelTitle[i] = '-';
545 /* correct trailing multipart level meta information in level name */
546 for (i = SP_LEVEL_NAME_LEN - 1;
547 i >= 0 && header->LevelTitle[i] == name_last;
549 header->LevelTitle[i] = '-';
552 /* ---------- check for normal single level ---------- */
554 if (!reading_multipart_level && !is_multipart_level)
556 /* the current level is simply a normal single-part level, and we are
557 not reading a multi-part level yet, so return the level as it is */
562 /* ---------- check for empty level (unused multi-part) ---------- */
564 if (!reading_multipart_level && is_multipart_level && !is_first_part)
566 /* this is a part of a multi-part level, but not the first part
567 (and we are not already reading parts of a multi-part level);
568 in this case, use an empty level instead of the single part */
570 use_empty_level = TRUE;
575 /* ---------- check for finished multi-part level ---------- */
577 if (reading_multipart_level &&
578 (!is_multipart_level ||
579 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
582 /* we are already reading parts of a multi-part level, but this level is
583 either not a multi-part level, or a part of a different multi-part
584 level; in both cases, the multi-part level seems to be complete */
589 /* ---------- here we have one part of a multi-part level ---------- */
591 reading_multipart_level = TRUE;
593 if (is_first_part) /* start with first part of new multi-part level */
595 /* copy level info structure from first part */
596 multipart_level = native_sp_level;
598 /* clear playfield of new multi-part level */
599 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
600 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
601 multipart_level.playfield[x][y] = fiSpace;
604 if (name_first == '?')
606 if (name_last == '?')
609 multipart_xpos = (int)(name_first - '0');
610 multipart_ypos = (int)(name_last - '0');
613 printf("----------> part (%d/%d) of multi-part level '%s'\n",
614 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
617 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
618 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
620 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
625 multipart_level.width = MAX(multipart_level.width,
626 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
627 multipart_level.height = MAX(multipart_level.height,
628 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
630 /* copy level part at the right position of multi-part level */
631 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
633 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
635 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
636 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
638 multipart_level.playfield[start_x + x][start_y + y] =
639 native_sp_level.playfield[x][y];
648 setLevelInfoToDefaults_SP();
650 Error(ERR_WARN, "single part of multi-part level -- using empty level");
653 if (reading_multipart_level)
654 native_sp_level = multipart_level;
656 copyInternalEngineVars_SP();