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.game_sp = &game_sp;
18 native_sp_level.width = SP_STD_PLAYFIELD_WIDTH;
19 native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
21 for (x = 0; x < native_sp_level.width; x++)
22 for (y = 0; y < native_sp_level.height; y++)
23 native_sp_level.playfield[x][y] = fiSpace;
25 /* copy string (without terminating '\0' character!) */
26 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
27 header->LevelTitle[i] = empty_title[i];
29 header->InitialGravity = 0;
31 header->InitialFreezeZonks = 0;
32 header->InfotronsNeeded = 0;
33 header->SpecialPortCount = 0;
34 header->SpeedByte = 0;
35 header->CheckSumByte = 0;
36 header->DemoRandomSeed = 0;
38 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
40 SpecialPortType *port = &header->SpecialPort[i];
42 port->PortLocation = 0;
44 port->FreezeZonks = 0;
45 port->FreezeEnemies = 0;
48 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
49 for (i = 0; i < SP_HEADER_SIZE; i++)
50 native_sp_level.header_raw_bytes[i] = 0x20;
52 native_sp_level.demo.is_available = FALSE;
53 native_sp_level.demo.length = 0;
56 void copyInternalEngineVars_SP()
58 char *preceding_playfield_memory[] =
60 "95 89 95 89 95 89 3b 8a 3b 8a 3b 8a 3b 8a 3b 8a", // |......;.;.;.;.;.|
61 "3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a e8 8a e8 8a", // |;.;.;.è.è.è.è.è.|
62 "e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b b1 8b b1 8b", // |è.è.è.±.±.±.±.±.|
63 "b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c 85 8c 85 8c", // |±.±.±...........|
64 "85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d 5b 8d 5b 8d", // |......[.[.[.[.[.|
65 "5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e 06 8e 06 8e", // |[.[.[...........|
66 "06 8e 06 8e 06 8e ac 8e ac 8e ac 8e ac 8e ac 8e", // |......¬.¬.¬.¬.¬.|
67 "ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f 59 8f 59 8f", // |¬.¬.¬.Y.Y.Y.Y.Y.|
68 "59 8f 59 8f 59 8f 00 00 70 13 00 00 00 00 e8 17", // |Y.Y.Y...p.....è.|
69 "00 00 00 00 00 00 69 38 00 00 00 00 00 00 00 00", // |......i8........|
70 "00 00 00 00 00 00 00 00 d0 86 00 00 b2 34 00 00", // |........Ð...²4..|
71 "00 00 00 00 00 00 8f 8b 1d 34 00 00 00 00 00 00", // |.........4......|
72 "00 00 00 00 23 39 09 09 00 0c 00 08 00 58 00 00", // |....#9.......X..|
73 "00 00 00 25 77 06 7f 00 00 00 01 00 00 00 00 00", // |...%w...........|
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 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 ec 06 26 05 00 00 00", // |.........ì.&....|
83 "00 00 00 01 00 00 00 00 31 32 33 34 35 36 37 38", // |........12345678|
84 "39 30 2d 00 08 00 51 57 45 52 54 59 55 49 4f 50", // |90-...QWERTYUIOP|
85 "00 00 0a 00 41 53 44 46 47 48 4a 4b 4c 00 00 00", // |....ASDFGHJKL...|
86 "00 00 5a 58 43 56 42 4e 4d 00 00 00 00 00 00 20", // |..ZXCVBNM...... |
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 00 00 00 00 00 00 00 00 00 00", // |................|
92 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
93 "00 00 00 00 00 00 2e 00 1e 00 31 00 14 00 39 00", // |..........1...9.|
94 "1f 00 14 00 18 00 ff ff 01 00 01 4c 45 56 45 4c", // |......ÿÿ...LEVEL|
95 "53 2e 44 41 54 00 00 00 00 00 00 00 00 00 00 00", // |S.DAT...........|
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", // |................|
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", // |................|
106 int preceding_buffer_size = 0;
111 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
112 preceding_buffer_size += 8; /* eight 16-bit integer values */
115 /* needed for engine snapshots */
116 game_sp.preceding_buffer_size = preceding_buffer_size;
118 LInfo = native_sp_level.header;
120 FieldWidth = native_sp_level.width;
121 FieldHeight = native_sp_level.height;
124 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
125 LevelMax = (FieldWidth * FieldHeight) - 1;
127 FileMax = FieldMax + native_sp_level.demo.length;
129 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
130 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
132 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
134 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
140 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
141 for (i = -FieldWidth; i < 0; i++)
142 PlayField16[i] = 0x20;
147 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
149 char *s = preceding_playfield_memory[i];
150 boolean hi_byte = FALSE; /* little endian data => start with low byte */
152 while (s[0] != '\0' && s[1] != '\0')
154 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
155 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
156 int byte = (hi_nibble << 4) | lo_nibble;
161 PlayField16[-preceding_buffer_size + count] |= byte;
177 for (y = 0; y < native_sp_level.height; y++)
178 for (x = 0; x < native_sp_level.width; x++)
179 PlayField8[count++] = native_sp_level.playfield[x][y];
181 /* add raw header bytes to subsequent playfield buffer zone */
182 for (i = 0; i < SP_HEADER_SIZE; i++)
183 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
185 for (i = 0; i < count; i++)
187 PlayField16[i] = PlayField8[i];
188 DisPlayField[i] = PlayField8[i];
199 printf("----------\n");
200 for (i = 0; i < preceding_buffer_size + FieldMax; i++)
202 int x = PlayField16[-preceding_buffer_size + i];
204 printf("%c%c", x & 0xff, x >> 8);
206 printf("----------\n");
216 for (i = 0; y = 0; y < native_sp_level.height; y++)
218 for (x = 0; x < native_sp_level.width; x++)
220 PlayField8[i] = native_sp_level.playfield[x][y];
222 PlayField16[i] = PlayField8[i];
223 DisPlayField[i] = PlayField8[i];
232 if (native_sp_level.demo.is_available)
234 DemoAvailable = True;
236 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
238 for (i = 0; i < native_sp_level.demo.length; i++)
239 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
242 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
243 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
244 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
246 DemoPointer = FieldMax + 1;
247 DemoOffset = DemoPointer;
248 DemoKeyRepeatCounter = 0;
250 GravityFlag = LInfo.InitialGravity;
251 FreezeZonks = LInfo.InitialFreezeZonks;
254 /* this is set by main game tape code to native random generator directly */
258 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
261 RandomSeed = LInfo.DemoRandomSeed;
268 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
269 boolean demo_available)
271 LevelInfoType *header = &native_sp_level.header;
274 /* for details of the Supaplex level format, see Herman Perk's Supaplex
275 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
277 native_sp_level.width = width;
278 native_sp_level.height = height;
280 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
281 for (y = 0; y < native_sp_level.height; y++)
282 for (x = 0; x < native_sp_level.width; x++)
283 native_sp_level.playfield[x][y] = getFile8Bit(file);
285 /* read level header (96 bytes) */
287 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
289 /* initial gravity: 1 == "on", anything else (0) == "off" */
290 header->InitialGravity = getFile8Bit(file);
292 /* SpeedFixVersion XOR 0x20 */
293 header->Version = getFile8Bit(file);
295 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
296 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
297 header->LevelTitle[i] = getFile8Bit(file);
299 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
300 header->InitialFreezeZonks = getFile8Bit(file);
302 /* number of infotrons needed; 0 means that Supaplex will count the total
303 amount of infotrons in the level and use the low byte of that number
304 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
305 header->InfotronsNeeded = getFile8Bit(file);
307 /* number of special ("gravity") port entries below (maximum 10 allowed) */
308 header->SpecialPortCount = getFile8Bit(file);
311 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
314 /* database of properties of up to 10 special ports (6 bytes per port) */
315 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
317 SpecialPortType *port = &header->SpecialPort[i];
319 /* high and low byte of the location of a special port; if (x, y) are the
320 coordinates of a port in the field and (0, 0) is the top-left corner,
321 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
322 of what may be expected: Supaplex works with a game field in memory
323 which is 2 bytes per tile) */
324 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
328 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
329 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
331 printf("::: %d: port_location == %d => (%d, %d)\n",
332 i, port->PortLocation, port_x, port_y);
336 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
337 port->Gravity = getFile8Bit(file);
339 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
340 port->FreezeZonks = getFile8Bit(file);
342 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
343 port->FreezeEnemies = getFile8Bit(file);
345 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
348 /* SpeedByte XOR Highbyte(RandomSeed) */
349 header->SpeedByte = getFile8Bit(file);
351 /* CheckSum XOR SpeedByte */
352 header->CheckSumByte = getFile8Bit(file);
354 /* random seed used for recorded demos */
355 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
356 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
359 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
362 /* auto-determine number of infotrons if it was stored as "0" -- see above */
363 if (header->InfotronsNeeded == 0)
365 for (x = 0; x < native_sp_level.width; x++)
366 for (y = 0; y < native_sp_level.height; y++)
367 if (native_sp_level.playfield[x][y] == fiInfotron)
368 header->InfotronsNeeded++;
370 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
373 /* read raw level header bytes (96 bytes) */
375 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
376 for (i = 0; i < SP_HEADER_SIZE; i++)
377 native_sp_level.header_raw_bytes[i] = fgetc(file);
379 /* also load demo tape, if available (only in single level files) */
383 int level_nr = getFile8Bit(file);
385 level_nr &= 0x7f; /* clear highest bit */
386 level_nr = (level_nr < 1 ? 1 :
387 level_nr > 111 ? 111 : level_nr);
389 native_sp_level.demo.level_nr = level_nr;
391 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
393 native_sp_level.demo.data[i] = getFile8Bit(file);
395 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
403 native_sp_level.demo.length = i;
404 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
408 boolean LoadNativeLevel_SP(char *filename, int level_pos)
412 char name_first, name_last;
413 struct LevelInfo_SP multipart_level;
414 int multipart_xpos, multipart_ypos;
415 boolean is_multipart_level;
416 boolean is_first_part;
417 boolean reading_multipart_level = FALSE;
418 boolean use_empty_level = FALSE;
419 LevelInfoType *header = &native_sp_level.header;
420 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
421 strSuffixLower(filename, ".mpx"));
422 boolean demo_available = is_single_level_file;
423 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
424 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
425 int level_width = SP_STD_PLAYFIELD_WIDTH;
426 int level_height = SP_STD_PLAYFIELD_HEIGHT;
428 /* always start with reliable default values */
429 setLevelInfoToDefaults_SP();
430 copyInternalEngineVars_SP();
432 if (!(file = fopen(filename, MODE_READ)))
434 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
441 char mpx_chunk_name[4 + 1];
444 LevelDescriptor *mpx_level_desc;
446 getFileChunkBE(file, mpx_chunk_name, NULL);
448 if (!strEqual(mpx_chunk_name, "MPX "))
450 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
456 mpx_version = getFile16BitLE(file);
458 if (mpx_version != 1)
460 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
466 mpx_level_count = getFile16BitLE(file);
468 if (mpx_level_count < 1)
470 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
476 if (level_pos >= mpx_level_count)
478 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
484 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
486 for (i = 0; i < mpx_level_count; i++)
488 LevelDescriptor *ldesc = &mpx_level_desc[i];
490 ldesc->Width = getFile16BitLE(file);
491 ldesc->Height = getFile16BitLE(file);
492 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
493 ldesc->Size = getFile32BitLE(file);
496 level_width = mpx_level_desc[level_pos].Width;
497 level_height = mpx_level_desc[level_pos].Height;
499 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
502 /* position file stream to the requested level (in case of level package) */
503 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
505 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
510 /* there exist Supaplex level package files with multi-part levels which
511 can be detected as follows: instead of leading and trailing dashes ('-')
512 to pad the level name, they have leading and trailing numbers which are
513 the x and y coordinations of the current part of the multi-part level;
514 if there are '?' characters instead of numbers on the left or right side
515 of the level name, the multi-part level consists of only horizontal or
518 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
520 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
523 /* check if this level is a part of a bigger multi-part level */
525 if (is_single_level_file)
528 name_first = header->LevelTitle[0];
529 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
532 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
533 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
536 ((name_first == '?' || name_first == '1') &&
537 (name_last == '?' || name_last == '1'));
539 if (is_multipart_level)
541 /* correct leading multipart level meta information in level name */
543 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
545 header->LevelTitle[i] = '-';
547 /* correct trailing multipart level meta information in level name */
548 for (i = SP_LEVEL_NAME_LEN - 1;
549 i >= 0 && header->LevelTitle[i] == name_last;
551 header->LevelTitle[i] = '-';
554 /* ---------- check for normal single level ---------- */
556 if (!reading_multipart_level && !is_multipart_level)
558 /* the current level is simply a normal single-part level, and we are
559 not reading a multi-part level yet, so return the level as it is */
564 /* ---------- check for empty level (unused multi-part) ---------- */
566 if (!reading_multipart_level && is_multipart_level && !is_first_part)
568 /* this is a part of a multi-part level, but not the first part
569 (and we are not already reading parts of a multi-part level);
570 in this case, use an empty level instead of the single part */
572 use_empty_level = TRUE;
577 /* ---------- check for finished multi-part level ---------- */
579 if (reading_multipart_level &&
580 (!is_multipart_level ||
581 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
584 /* we are already reading parts of a multi-part level, but this level is
585 either not a multi-part level, or a part of a different multi-part
586 level; in both cases, the multi-part level seems to be complete */
591 /* ---------- here we have one part of a multi-part level ---------- */
593 reading_multipart_level = TRUE;
595 if (is_first_part) /* start with first part of new multi-part level */
597 /* copy level info structure from first part */
598 multipart_level = native_sp_level;
600 /* clear playfield of new multi-part level */
601 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
602 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
603 multipart_level.playfield[x][y] = fiSpace;
606 if (name_first == '?')
608 if (name_last == '?')
611 multipart_xpos = (int)(name_first - '0');
612 multipart_ypos = (int)(name_last - '0');
615 printf("----------> part (%d/%d) of multi-part level '%s'\n",
616 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
619 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
620 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
622 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
627 multipart_level.width = MAX(multipart_level.width,
628 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
629 multipart_level.height = MAX(multipart_level.height,
630 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
632 /* copy level part at the right position of multi-part level */
633 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
635 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
637 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
638 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
640 multipart_level.playfield[start_x + x][start_y + y] =
641 native_sp_level.playfield[x][y];
650 setLevelInfoToDefaults_SP();
652 Error(ERR_WARN, "single part of multi-part level -- using empty level");
655 if (reading_multipart_level)
656 native_sp_level = multipart_level;
658 copyInternalEngineVars_SP();