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;
133 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
134 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
135 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
138 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
140 char *s = preceding_playfield_memory[i];
141 boolean hi_byte = FALSE; /* little endian data => start with low byte */
143 while (s[0] != '\0' && s[1] != '\0')
145 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
146 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
147 int byte = (hi_nibble << 4) | lo_nibble;
152 PlayField16[-preceding_buffer_size + count] |= byte;
167 for (y = 0; y < native_sp_level.height; y++)
168 for (x = 0; x < native_sp_level.width; x++)
169 PlayField8[count++] = native_sp_level.playfield[x][y];
171 /* add raw header bytes to subsequent playfield buffer zone */
172 for (i = 0; i < SP_HEADER_SIZE; i++)
173 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
175 for (i = 0; i < count; i++)
177 PlayField16[i] = PlayField8[i];
178 DisPlayField[i] = PlayField8[i];
182 if (native_sp_level.demo.is_available)
184 DemoAvailable = True;
186 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
188 for (i = 0; i < native_sp_level.demo.length; i++)
189 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
192 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
193 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
194 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
196 GravityFlag = LInfo.InitialGravity;
197 FreezeZonks = LInfo.InitialFreezeZonks;
200 /* this is set by main game tape code to native random generator directly */
202 RandomSeed = LInfo.DemoRandomSeed;
208 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
209 boolean demo_available)
211 LevelInfoType *header = &native_sp_level.header;
214 /* for details of the Supaplex level format, see Herman Perk's Supaplex
215 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
217 native_sp_level.width = width;
218 native_sp_level.height = height;
220 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
221 for (y = 0; y < native_sp_level.height; y++)
222 for (x = 0; x < native_sp_level.width; x++)
223 native_sp_level.playfield[x][y] = getFile8Bit(file);
225 /* read level header (96 bytes) */
227 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
229 /* initial gravity: 1 == "on", anything else (0) == "off" */
230 header->InitialGravity = getFile8Bit(file);
232 /* SpeedFixVersion XOR 0x20 */
233 header->Version = getFile8Bit(file);
235 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
236 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
237 header->LevelTitle[i] = getFile8Bit(file);
239 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
240 header->InitialFreezeZonks = getFile8Bit(file);
242 /* number of infotrons needed; 0 means that Supaplex will count the total
243 amount of infotrons in the level and use the low byte of that number
244 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
245 header->InfotronsNeeded = getFile8Bit(file);
247 /* number of special ("gravity") port entries below (maximum 10 allowed) */
248 header->SpecialPortCount = getFile8Bit(file);
250 /* database of properties of up to 10 special ports (6 bytes per port) */
251 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
253 SpecialPortType *port = &header->SpecialPort[i];
255 /* high and low byte of the location of a special port; if (x, y) are the
256 coordinates of a port in the field and (0, 0) is the top-left corner,
257 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
258 of what may be expected: Supaplex works with a game field in memory
259 which is 2 bytes per tile) */
260 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
262 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
263 port->Gravity = getFile8Bit(file);
265 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
266 port->FreezeZonks = getFile8Bit(file);
268 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
269 port->FreezeEnemies = getFile8Bit(file);
271 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
274 /* SpeedByte XOR Highbyte(RandomSeed) */
275 header->SpeedByte = getFile8Bit(file);
277 /* CheckSum XOR SpeedByte */
278 header->CheckSumByte = getFile8Bit(file);
280 /* random seed used for recorded demos */
281 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
283 /* auto-determine number of infotrons if it was stored as "0" -- see above */
284 if (header->InfotronsNeeded == 0)
286 for (x = 0; x < native_sp_level.width; x++)
287 for (y = 0; y < native_sp_level.height; y++)
288 if (native_sp_level.playfield[x][y] == fiInfotron)
289 header->InfotronsNeeded++;
291 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
294 /* read raw level header bytes (96 bytes) */
296 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
297 for (i = 0; i < SP_HEADER_SIZE; i++)
298 native_sp_level.header_raw_bytes[i] = fgetc(file);
300 /* also load demo tape, if available (only in single level files) */
304 int level_nr = getFile8Bit(file);
306 level_nr &= 0x7f; /* clear highest bit */
307 level_nr = (level_nr < 1 ? 1 :
308 level_nr > 111 ? 111 : level_nr);
310 native_sp_level.demo.level_nr = level_nr;
312 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
314 native_sp_level.demo.data[i] = getFile8Bit(file);
316 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
324 native_sp_level.demo.length = i;
325 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
329 boolean LoadNativeLevel_SP(char *filename, int level_pos)
333 char name_first, name_last;
334 struct LevelInfo_SP multipart_level;
335 int multipart_xpos, multipart_ypos;
336 boolean is_multipart_level;
337 boolean is_first_part;
338 boolean reading_multipart_level = FALSE;
339 boolean use_empty_level = FALSE;
340 LevelInfoType *header = &native_sp_level.header;
341 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
342 strSuffixLower(filename, ".mpx"));
343 boolean demo_available = is_single_level_file;
344 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
345 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
346 int level_width = SP_STD_PLAYFIELD_WIDTH;
347 int level_height = SP_STD_PLAYFIELD_HEIGHT;
349 /* always start with reliable default values */
350 setLevelInfoToDefaults_SP();
351 copyInternalEngineVars_SP();
353 if (!(file = fopen(filename, MODE_READ)))
355 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
362 char mpx_chunk_name[4 + 1];
365 LevelDescriptor *mpx_level_desc;
367 getFileChunkBE(file, mpx_chunk_name, NULL);
369 if (!strEqual(mpx_chunk_name, "MPX "))
371 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
377 mpx_version = getFile16BitLE(file);
379 if (mpx_version != 1)
381 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
387 mpx_level_count = getFile16BitLE(file);
389 if (mpx_level_count < 1)
391 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
397 if (level_pos >= mpx_level_count)
399 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
405 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
407 for (i = 0; i < mpx_level_count; i++)
409 LevelDescriptor *ldesc = &mpx_level_desc[i];
411 ldesc->Width = getFile16BitLE(file);
412 ldesc->Height = getFile16BitLE(file);
413 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
414 ldesc->Size = getFile32BitLE(file);
417 level_width = mpx_level_desc[level_pos].Width;
418 level_height = mpx_level_desc[level_pos].Height;
420 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
423 /* position file stream to the requested level (in case of level package) */
424 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
426 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
431 /* there exist Supaplex level package files with multi-part levels which
432 can be detected as follows: instead of leading and trailing dashes ('-')
433 to pad the level name, they have leading and trailing numbers which are
434 the x and y coordinations of the current part of the multi-part level;
435 if there are '?' characters instead of numbers on the left or right side
436 of the level name, the multi-part level consists of only horizontal or
439 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
441 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
444 /* check if this level is a part of a bigger multi-part level */
446 if (is_single_level_file)
449 name_first = header->LevelTitle[0];
450 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
453 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
454 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
457 ((name_first == '?' || name_first == '1') &&
458 (name_last == '?' || name_last == '1'));
460 if (is_multipart_level)
462 /* correct leading multipart level meta information in level name */
464 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
466 header->LevelTitle[i] = '-';
468 /* correct trailing multipart level meta information in level name */
469 for (i = SP_LEVEL_NAME_LEN - 1;
470 i >= 0 && header->LevelTitle[i] == name_last;
472 header->LevelTitle[i] = '-';
475 /* ---------- check for normal single level ---------- */
477 if (!reading_multipart_level && !is_multipart_level)
479 /* the current level is simply a normal single-part level, and we are
480 not reading a multi-part level yet, so return the level as it is */
485 /* ---------- check for empty level (unused multi-part) ---------- */
487 if (!reading_multipart_level && is_multipart_level && !is_first_part)
489 /* this is a part of a multi-part level, but not the first part
490 (and we are not already reading parts of a multi-part level);
491 in this case, use an empty level instead of the single part */
493 use_empty_level = TRUE;
498 /* ---------- check for finished multi-part level ---------- */
500 if (reading_multipart_level &&
501 (!is_multipart_level ||
502 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
505 /* we are already reading parts of a multi-part level, but this level is
506 either not a multi-part level, or a part of a different multi-part
507 level; in both cases, the multi-part level seems to be complete */
512 /* ---------- here we have one part of a multi-part level ---------- */
514 reading_multipart_level = TRUE;
516 if (is_first_part) /* start with first part of new multi-part level */
518 /* copy level info structure from first part */
519 multipart_level = native_sp_level;
521 /* clear playfield of new multi-part level */
522 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
523 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
524 multipart_level.playfield[x][y] = fiSpace;
527 if (name_first == '?')
529 if (name_last == '?')
532 multipart_xpos = (int)(name_first - '0');
533 multipart_ypos = (int)(name_last - '0');
536 printf("----------> part (%d/%d) of multi-part level '%s'\n",
537 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
540 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
541 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
543 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
548 multipart_level.width = MAX(multipart_level.width,
549 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
550 multipart_level.height = MAX(multipart_level.height,
551 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
553 /* copy level part at the right position of multi-part level */
554 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
556 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
558 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
559 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
561 multipart_level.playfield[start_x + x][start_y + y] =
562 native_sp_level.playfield[x][y];
571 setLevelInfoToDefaults_SP();
573 Error(ERR_WARN, "single part of multi-part level -- using empty level");
576 if (reading_multipart_level)
577 native_sp_level = multipart_level;
579 copyInternalEngineVars_SP();
584 void SaveNativeLevel_SP(char *filename)
586 LevelInfoType *header = &native_sp_level.header;
590 if (!(file = fopen(filename, MODE_WRITE)))
592 Error(ERR_WARN, "cannot save native level file '%s'", filename);
597 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
598 for (y = 0; y < native_sp_level.height; y++)
599 for (x = 0; x < native_sp_level.width; x++)
600 putFile8Bit(file, native_sp_level.playfield[x][y]);
602 /* write level header (96 bytes) */
604 WriteUnusedBytesToFile(file, 4);
606 putFile8Bit(file, header->InitialGravity);
607 putFile8Bit(file, header->Version);
609 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
610 putFile8Bit(file, header->LevelTitle[i]);
612 putFile8Bit(file, header->InitialFreezeZonks);
613 putFile8Bit(file, header->InfotronsNeeded);
614 putFile8Bit(file, header->SpecialPortCount);
616 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
618 SpecialPortType *port = &header->SpecialPort[i];
620 putFile16BitBE(file, port->PortLocation);
621 putFile8Bit(file, port->Gravity);
622 putFile8Bit(file, port->FreezeZonks);
623 putFile8Bit(file, port->FreezeEnemies);
625 WriteUnusedBytesToFile(file, 1);
628 putFile8Bit(file, header->SpeedByte);
629 putFile8Bit(file, header->CheckSumByte);
630 putFile16BitLE(file, header->DemoRandomSeed);
632 /* also save demo tape, if available */
634 if (native_sp_level.demo.is_available)
636 putFile8Bit(file, native_sp_level.demo.level_nr);
638 for (i = 0; i < native_sp_level.demo.length; i++)
639 putFile8Bit(file, native_sp_level.demo.data[i]);