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 /* (add one byte for the level number stored as first byte of demo data) */
131 FileMax = FieldMax + native_sp_level.demo.length + 1;
134 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
135 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
136 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
140 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
142 char *s = preceding_playfield_memory[i];
143 boolean hi_byte = FALSE; /* little endian data => start with low byte */
145 while (s[0] != '\0' && s[1] != '\0')
147 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
148 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
149 int byte = (hi_nibble << 4) | lo_nibble;
154 PlayField16[-preceding_buffer_size + count] |= byte;
169 for (y = 0; y < native_sp_level.height; y++)
170 for (x = 0; x < native_sp_level.width; x++)
171 PlayField8[count++] = native_sp_level.playfield[x][y];
173 /* add raw header bytes to subsequent playfield buffer zone */
174 for (i = 0; i < SP_HEADER_SIZE; i++)
175 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
177 for (i = 0; i < count; i++)
179 PlayField16[i] = PlayField8[i];
180 DisPlayField[i] = PlayField8[i];
184 if (native_sp_level.demo.is_available)
186 DemoAvailable = True;
189 /* !!! NEVER USED !!! */
190 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
192 /* !!! NEVER USED !!! */
193 for (i = 0; i < native_sp_level.demo.length; i++)
194 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
199 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
200 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
201 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
204 GravityFlag = LInfo.InitialGravity;
205 FreezeZonks = LInfo.InitialFreezeZonks;
208 /* this is set by main game tape code to native random generator directly */
210 RandomSeed = LInfo.DemoRandomSeed;
216 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
217 boolean demo_available)
219 LevelInfoType *header = &native_sp_level.header;
222 /* for details of the Supaplex level format, see Herman Perk's Supaplex
223 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
225 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
226 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
228 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
229 /* (MPX levels may have non-standard playfield size -- check max. size) */
230 for (y = 0; y < height; y++)
232 for (x = 0; x < width; x++)
234 byte element = getFile8Bit(file);
236 if (x < SP_MAX_PLAYFIELD_WIDTH &&
237 y < SP_MAX_PLAYFIELD_HEIGHT)
238 native_sp_level.playfield[x][y] = element;
242 /* read level header (96 bytes) */
244 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
246 /* initial gravity: 1 == "on", anything else (0) == "off" */
247 header->InitialGravity = getFile8Bit(file);
249 /* SpeedFixVersion XOR 0x20 */
250 header->Version = getFile8Bit(file);
252 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
253 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
254 header->LevelTitle[i] = getFile8Bit(file);
256 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
257 header->InitialFreezeZonks = getFile8Bit(file);
259 /* number of infotrons needed; 0 means that Supaplex will count the total
260 amount of infotrons in the level and use the low byte of that number
261 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
262 header->InfotronsNeeded = getFile8Bit(file);
264 /* number of special ("gravity") port entries below (maximum 10 allowed) */
265 header->SpecialPortCount = getFile8Bit(file);
267 /* database of properties of up to 10 special ports (6 bytes per port) */
268 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
270 SpecialPortType *port = &header->SpecialPort[i];
272 /* high and low byte of the location of a special port; if (x, y) are the
273 coordinates of a port in the field and (0, 0) is the top-left corner,
274 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
275 of what may be expected: Supaplex works with a game field in memory
276 which is 2 bytes per tile) */
277 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
279 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
280 port->Gravity = getFile8Bit(file);
282 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
283 port->FreezeZonks = getFile8Bit(file);
285 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
286 port->FreezeEnemies = getFile8Bit(file);
288 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
291 /* SpeedByte XOR Highbyte(RandomSeed) */
292 header->SpeedByte = getFile8Bit(file);
294 /* CheckSum XOR SpeedByte */
295 header->CheckSumByte = getFile8Bit(file);
297 /* random seed used for recorded demos */
298 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
300 /* auto-determine number of infotrons if it was stored as "0" -- see above */
301 if (header->InfotronsNeeded == 0)
303 for (x = 0; x < native_sp_level.width; x++)
304 for (y = 0; y < native_sp_level.height; y++)
305 if (native_sp_level.playfield[x][y] == fiInfotron)
306 header->InfotronsNeeded++;
308 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
311 /* read raw level header bytes (96 bytes) */
313 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
314 for (i = 0; i < SP_HEADER_SIZE; i++)
315 native_sp_level.header_raw_bytes[i] = fgetc(file);
317 /* also load demo tape, if available (only in single level files) */
321 int level_nr = getFile8Bit(file);
323 level_nr &= 0x7f; /* clear highest bit */
324 level_nr = (level_nr < 1 ? 1 :
325 level_nr > 111 ? 111 : level_nr);
327 native_sp_level.demo.level_nr = level_nr;
329 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
331 native_sp_level.demo.data[i] = getFile8Bit(file);
333 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
341 native_sp_level.demo.length = i;
342 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
346 boolean LoadNativeLevel_SP(char *filename, int level_pos)
350 char name_first, name_last;
351 struct LevelInfo_SP multipart_level;
352 int multipart_xpos, multipart_ypos;
353 boolean is_multipart_level;
354 boolean is_first_part;
355 boolean reading_multipart_level = FALSE;
356 boolean use_empty_level = FALSE;
357 LevelInfoType *header = &native_sp_level.header;
358 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
359 strSuffixLower(filename, ".mpx"));
360 boolean demo_available = is_single_level_file;
361 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
362 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
363 int level_width = SP_STD_PLAYFIELD_WIDTH;
364 int level_height = SP_STD_PLAYFIELD_HEIGHT;
366 /* always start with reliable default values */
367 setLevelInfoToDefaults_SP();
368 copyInternalEngineVars_SP();
370 if (!(file = fopen(filename, MODE_READ)))
372 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
379 char mpx_chunk_name[4 + 1];
382 LevelDescriptor *mpx_level_desc;
384 getFileChunkBE(file, mpx_chunk_name, NULL);
386 if (!strEqual(mpx_chunk_name, "MPX "))
388 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
394 mpx_version = getFile16BitLE(file);
396 if (mpx_version != 1)
398 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
404 mpx_level_count = getFile16BitLE(file);
406 if (mpx_level_count < 1)
408 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
414 if (level_pos >= mpx_level_count)
416 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
422 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
424 for (i = 0; i < mpx_level_count; i++)
426 LevelDescriptor *ldesc = &mpx_level_desc[i];
428 ldesc->Width = getFile16BitLE(file);
429 ldesc->Height = getFile16BitLE(file);
430 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
431 ldesc->Size = getFile32BitLE(file);
434 level_width = mpx_level_desc[level_pos].Width;
435 level_height = mpx_level_desc[level_pos].Height;
437 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
440 /* position file stream to the requested level (in case of level package) */
441 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
443 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
448 /* there exist Supaplex level package files with multi-part levels which
449 can be detected as follows: instead of leading and trailing dashes ('-')
450 to pad the level name, they have leading and trailing numbers which are
451 the x and y coordinations of the current part of the multi-part level;
452 if there are '?' characters instead of numbers on the left or right side
453 of the level name, the multi-part level consists of only horizontal or
456 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
458 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
461 /* check if this level is a part of a bigger multi-part level */
463 if (is_single_level_file)
466 name_first = header->LevelTitle[0];
467 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
470 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
471 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
474 ((name_first == '?' || name_first == '1') &&
475 (name_last == '?' || name_last == '1'));
477 if (is_multipart_level)
479 /* correct leading multipart level meta information in level name */
481 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
483 header->LevelTitle[i] = '-';
485 /* correct trailing multipart level meta information in level name */
486 for (i = SP_LEVEL_NAME_LEN - 1;
487 i >= 0 && header->LevelTitle[i] == name_last;
489 header->LevelTitle[i] = '-';
492 /* ---------- check for normal single level ---------- */
494 if (!reading_multipart_level && !is_multipart_level)
496 /* the current level is simply a normal single-part level, and we are
497 not reading a multi-part level yet, so return the level as it is */
502 /* ---------- check for empty level (unused multi-part) ---------- */
504 if (!reading_multipart_level && is_multipart_level && !is_first_part)
506 /* this is a part of a multi-part level, but not the first part
507 (and we are not already reading parts of a multi-part level);
508 in this case, use an empty level instead of the single part */
510 use_empty_level = TRUE;
515 /* ---------- check for finished multi-part level ---------- */
517 if (reading_multipart_level &&
518 (!is_multipart_level ||
519 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
522 /* we are already reading parts of a multi-part level, but this level is
523 either not a multi-part level, or a part of a different multi-part
524 level; in both cases, the multi-part level seems to be complete */
529 /* ---------- here we have one part of a multi-part level ---------- */
531 reading_multipart_level = TRUE;
533 if (is_first_part) /* start with first part of new multi-part level */
535 /* copy level info structure from first part */
536 multipart_level = native_sp_level;
538 /* clear playfield of new multi-part level */
539 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
540 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
541 multipart_level.playfield[x][y] = fiSpace;
544 if (name_first == '?')
546 if (name_last == '?')
549 multipart_xpos = (int)(name_first - '0');
550 multipart_ypos = (int)(name_last - '0');
553 printf("----------> part (%d/%d) of multi-part level '%s'\n",
554 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
557 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
558 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
560 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
565 multipart_level.width = MAX(multipart_level.width,
566 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
567 multipart_level.height = MAX(multipart_level.height,
568 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
570 /* copy level part at the right position of multi-part level */
571 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
573 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
575 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
576 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
578 multipart_level.playfield[start_x + x][start_y + y] =
579 native_sp_level.playfield[x][y];
588 setLevelInfoToDefaults_SP();
590 Error(ERR_WARN, "single part of multi-part level -- using empty level");
593 if (reading_multipart_level)
594 native_sp_level = multipart_level;
596 copyInternalEngineVars_SP();
601 void SaveNativeLevel_SP(char *filename)
603 LevelInfoType *header = &native_sp_level.header;
607 if (!(file = fopen(filename, MODE_WRITE)))
609 Error(ERR_WARN, "cannot save native level file '%s'", filename);
614 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
615 for (y = 0; y < native_sp_level.height; y++)
616 for (x = 0; x < native_sp_level.width; x++)
617 putFile8Bit(file, native_sp_level.playfield[x][y]);
619 /* write level header (96 bytes) */
621 WriteUnusedBytesToFile(file, 4);
623 putFile8Bit(file, header->InitialGravity);
624 putFile8Bit(file, header->Version);
626 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
627 putFile8Bit(file, header->LevelTitle[i]);
629 putFile8Bit(file, header->InitialFreezeZonks);
630 putFile8Bit(file, header->InfotronsNeeded);
631 putFile8Bit(file, header->SpecialPortCount);
633 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
635 SpecialPortType *port = &header->SpecialPort[i];
637 putFile16BitBE(file, port->PortLocation);
638 putFile8Bit(file, port->Gravity);
639 putFile8Bit(file, port->FreezeZonks);
640 putFile8Bit(file, port->FreezeEnemies);
642 WriteUnusedBytesToFile(file, 1);
645 putFile8Bit(file, header->SpeedByte);
646 putFile8Bit(file, header->CheckSumByte);
647 putFile16BitLE(file, header->DemoRandomSeed);
649 /* also save demo tape, if available */
651 if (native_sp_level.demo.is_available)
653 putFile8Bit(file, native_sp_level.demo.level_nr);
655 for (i = 0; i < native_sp_level.demo.length; i++)
656 putFile8Bit(file, native_sp_level.demo.data[i]);