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()
66 LInfo = native_sp_level.header;
68 FieldWidth = native_sp_level.width;
69 FieldHeight = native_sp_level.height;
72 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
73 LevelMax = (FieldWidth * FieldHeight) - 1;
76 /* (add one byte for the level number stored as first byte of demo data) */
77 FileMax = FieldMax + native_sp_level.demo.length + 1;
81 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
82 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
83 PlayField16 = REDIM_1D(sizeof(int), -game_sp.preceding_buffer_size, FieldMax);
86 /* initialize preceding playfield buffer */
87 for (i = -game_sp.preceding_buffer_size; i < 0; i++)
90 /* initialize preceding playfield buffer */
91 for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++)
95 for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
97 char *s = game_sp.preceding_buffer[i];
98 boolean hi_byte = FALSE; /* little endian data => start with low byte */
100 while (s[0] != '\0' && s[1] != '\0')
102 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
103 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
104 int byte = (hi_nibble << 4) | lo_nibble;
109 PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
124 for (y = 0; y < native_sp_level.height; y++)
125 for (x = 0; x < native_sp_level.width; x++)
126 PlayField8[count++] = native_sp_level.playfield[x][y];
128 /* add raw header bytes to subsequent playfield buffer zone */
129 for (i = 0; i < SP_HEADER_SIZE; i++)
130 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
132 for (i = 0; i < count; i++)
134 PlayField16[i] = PlayField8[i];
135 DisPlayField[i] = PlayField8[i];
139 if (native_sp_level.demo.is_available)
141 DemoAvailable = True;
144 /* !!! NEVER USED !!! */
145 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
147 /* !!! NEVER USED !!! */
148 for (i = 0; i < native_sp_level.demo.length; i++)
149 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
154 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
155 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
156 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
159 GravityFlag = LInfo.InitialGravity;
160 FreezeZonks = LInfo.InitialFreezeZonks;
163 /* this is set by main game tape code to native random generator directly */
165 RandomSeed = LInfo.DemoRandomSeed;
173 static void LoadNativeLevelFromFileStream_SP(File *file, int width, int height,
174 boolean demo_available)
176 LevelInfoType *header = &native_sp_level.header;
179 /* for details of the Supaplex level format, see Herman Perk's Supaplex
180 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
182 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
183 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
185 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
186 /* (MPX levels may have non-standard playfield size -- check max. size) */
187 for (y = 0; y < height; y++)
189 for (x = 0; x < width; x++)
191 byte element = getFile8Bit(file);
193 if (x < SP_MAX_PLAYFIELD_WIDTH &&
194 y < SP_MAX_PLAYFIELD_HEIGHT)
195 native_sp_level.playfield[x][y] = element;
199 /* read level header (96 bytes) */
201 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
203 /* initial gravity: 1 == "on", anything else (0) == "off" */
204 header->InitialGravity = getFile8Bit(file);
206 /* SpeedFixVersion XOR 0x20 */
207 header->Version = getFile8Bit(file);
209 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
210 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
211 header->LevelTitle[i] = getFile8Bit(file);
213 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
214 header->InitialFreezeZonks = getFile8Bit(file);
216 /* number of infotrons needed; 0 means that Supaplex will count the total
217 amount of infotrons in the level and use the low byte of that number
218 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
219 header->InfotronsNeeded = getFile8Bit(file);
221 /* number of special ("gravity") port entries below (maximum 10 allowed) */
222 header->SpecialPortCount = getFile8Bit(file);
224 /* database of properties of up to 10 special ports (6 bytes per port) */
225 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
227 SpecialPortType *port = &header->SpecialPort[i];
229 /* high and low byte of the location of a special port; if (x, y) are the
230 coordinates of a port in the field and (0, 0) is the top-left corner,
231 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
232 of what may be expected: Supaplex works with a game field in memory
233 which is 2 bytes per tile) */
234 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
236 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
237 port->Gravity = getFile8Bit(file);
239 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
240 port->FreezeZonks = getFile8Bit(file);
242 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
243 port->FreezeEnemies = getFile8Bit(file);
245 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
248 /* SpeedByte XOR Highbyte(RandomSeed) */
249 header->SpeedByte = getFile8Bit(file);
251 /* CheckSum XOR SpeedByte */
252 header->CheckSumByte = getFile8Bit(file);
254 /* random seed used for recorded demos */
255 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
257 /* auto-determine number of infotrons if it was stored as "0" -- see above */
258 if (header->InfotronsNeeded == 0)
260 for (x = 0; x < native_sp_level.width; x++)
261 for (y = 0; y < native_sp_level.height; y++)
262 if (native_sp_level.playfield[x][y] == fiInfotron)
263 header->InfotronsNeeded++;
265 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
268 /* read raw level header bytes (96 bytes) */
270 seekFile(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
271 for (i = 0; i < SP_HEADER_SIZE; i++)
272 native_sp_level.header_raw_bytes[i] = getByteFromFile(file);
274 /* also load demo tape, if available (only in single level files) */
278 int level_nr = getFile8Bit(file);
280 level_nr &= 0x7f; /* clear highest bit */
281 level_nr = (level_nr < 1 ? 1 :
282 level_nr > 111 ? 111 : level_nr);
284 native_sp_level.demo.level_nr = level_nr;
286 for (i = 0; i < SP_MAX_TAPE_LEN && !checkEndOfFile(file); i++)
288 native_sp_level.demo.data[i] = getFile8Bit(file);
290 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
298 native_sp_level.demo.length = i;
299 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
305 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
306 boolean demo_available)
308 LevelInfoType *header = &native_sp_level.header;
311 /* for details of the Supaplex level format, see Herman Perk's Supaplex
312 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
314 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
315 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
317 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
318 /* (MPX levels may have non-standard playfield size -- check max. size) */
319 for (y = 0; y < height; y++)
321 for (x = 0; x < width; x++)
323 byte element = getFile8Bit(file);
325 if (x < SP_MAX_PLAYFIELD_WIDTH &&
326 y < SP_MAX_PLAYFIELD_HEIGHT)
327 native_sp_level.playfield[x][y] = element;
331 /* read level header (96 bytes) */
333 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
335 /* initial gravity: 1 == "on", anything else (0) == "off" */
336 header->InitialGravity = getFile8Bit(file);
338 /* SpeedFixVersion XOR 0x20 */
339 header->Version = getFile8Bit(file);
341 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
342 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
343 header->LevelTitle[i] = getFile8Bit(file);
345 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
346 header->InitialFreezeZonks = getFile8Bit(file);
348 /* number of infotrons needed; 0 means that Supaplex will count the total
349 amount of infotrons in the level and use the low byte of that number
350 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
351 header->InfotronsNeeded = getFile8Bit(file);
353 /* number of special ("gravity") port entries below (maximum 10 allowed) */
354 header->SpecialPortCount = getFile8Bit(file);
356 /* database of properties of up to 10 special ports (6 bytes per port) */
357 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
359 SpecialPortType *port = &header->SpecialPort[i];
361 /* high and low byte of the location of a special port; if (x, y) are the
362 coordinates of a port in the field and (0, 0) is the top-left corner,
363 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
364 of what may be expected: Supaplex works with a game field in memory
365 which is 2 bytes per tile) */
366 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
368 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
369 port->Gravity = getFile8Bit(file);
371 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
372 port->FreezeZonks = getFile8Bit(file);
374 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
375 port->FreezeEnemies = getFile8Bit(file);
377 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
380 /* SpeedByte XOR Highbyte(RandomSeed) */
381 header->SpeedByte = getFile8Bit(file);
383 /* CheckSum XOR SpeedByte */
384 header->CheckSumByte = getFile8Bit(file);
386 /* random seed used for recorded demos */
387 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
389 /* auto-determine number of infotrons if it was stored as "0" -- see above */
390 if (header->InfotronsNeeded == 0)
392 for (x = 0; x < native_sp_level.width; x++)
393 for (y = 0; y < native_sp_level.height; y++)
394 if (native_sp_level.playfield[x][y] == fiInfotron)
395 header->InfotronsNeeded++;
397 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
400 /* read raw level header bytes (96 bytes) */
402 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
403 for (i = 0; i < SP_HEADER_SIZE; i++)
404 native_sp_level.header_raw_bytes[i] = fgetc(file);
406 /* also load demo tape, if available (only in single level files) */
410 int level_nr = getFile8Bit(file);
412 level_nr &= 0x7f; /* clear highest bit */
413 level_nr = (level_nr < 1 ? 1 :
414 level_nr > 111 ? 111 : level_nr);
416 native_sp_level.demo.level_nr = level_nr;
418 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
420 native_sp_level.demo.data[i] = getFile8Bit(file);
422 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
430 native_sp_level.demo.length = i;
431 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
439 boolean LoadNativeLevel_SP(char *filename, int level_pos,
440 boolean level_info_only)
444 char name_first, name_last;
445 struct LevelInfo_SP multipart_level;
446 int multipart_xpos, multipart_ypos;
447 boolean is_multipart_level;
448 boolean is_first_part;
449 boolean reading_multipart_level = FALSE;
450 boolean use_empty_level = FALSE;
451 LevelInfoType *header = &native_sp_level.header;
452 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
453 strSuffixLower(filename, ".mpx"));
454 boolean demo_available = is_single_level_file;
455 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
456 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
457 int level_width = SP_STD_PLAYFIELD_WIDTH;
458 int level_height = SP_STD_PLAYFIELD_HEIGHT;
460 /* always start with reliable default values */
461 setLevelInfoToDefaults_SP();
462 copyInternalEngineVars_SP();
464 if (!(file = openFile(filename, MODE_READ)))
466 if (!level_info_only)
467 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
474 char mpx_chunk_name[4 + 1];
477 LevelDescriptor *mpx_level_desc;
479 getFileChunkBE(file, mpx_chunk_name, NULL);
481 if (!strEqual(mpx_chunk_name, "MPX "))
483 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
489 mpx_version = getFile16BitLE(file);
491 if (mpx_version != 1)
493 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
499 mpx_level_count = getFile16BitLE(file);
501 if (mpx_level_count < 1)
503 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
509 if (level_pos >= mpx_level_count)
511 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
517 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
519 for (i = 0; i < mpx_level_count; i++)
521 LevelDescriptor *ldesc = &mpx_level_desc[i];
523 ldesc->Width = getFile16BitLE(file);
524 ldesc->Height = getFile16BitLE(file);
525 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
526 ldesc->Size = getFile32BitLE(file);
529 level_width = mpx_level_desc[level_pos].Width;
530 level_height = mpx_level_desc[level_pos].Height;
532 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
535 /* position file stream to the requested level (in case of level package) */
536 if (seekFile(file, file_seek_pos, SEEK_SET) != 0)
538 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
543 /* there exist Supaplex level package files with multi-part levels which
544 can be detected as follows: instead of leading and trailing dashes ('-')
545 to pad the level name, they have leading and trailing numbers which are
546 the x and y coordinations of the current part of the multi-part level;
547 if there are '?' characters instead of numbers on the left or right side
548 of the level name, the multi-part level consists of only horizontal or
551 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
553 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
556 /* check if this level is a part of a bigger multi-part level */
558 if (is_single_level_file)
561 name_first = header->LevelTitle[0];
562 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
565 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
566 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
569 ((name_first == '?' || name_first == '1') &&
570 (name_last == '?' || name_last == '1'));
572 if (is_multipart_level)
574 /* correct leading multipart level meta information in level name */
576 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
578 header->LevelTitle[i] = '-';
580 /* correct trailing multipart level meta information in level name */
581 for (i = SP_LEVEL_NAME_LEN - 1;
582 i >= 0 && header->LevelTitle[i] == name_last;
584 header->LevelTitle[i] = '-';
587 /* ---------- check for normal single level ---------- */
589 if (!reading_multipart_level && !is_multipart_level)
591 /* the current level is simply a normal single-part level, and we are
592 not reading a multi-part level yet, so return the level as it is */
597 /* ---------- check for empty level (unused multi-part) ---------- */
599 if (!reading_multipart_level && is_multipart_level && !is_first_part)
601 /* this is a part of a multi-part level, but not the first part
602 (and we are not already reading parts of a multi-part level);
603 in this case, use an empty level instead of the single part */
605 use_empty_level = TRUE;
610 /* ---------- check for finished multi-part level ---------- */
612 if (reading_multipart_level &&
613 (!is_multipart_level ||
614 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
617 /* we are already reading parts of a multi-part level, but this level is
618 either not a multi-part level, or a part of a different multi-part
619 level; in both cases, the multi-part level seems to be complete */
624 /* ---------- here we have one part of a multi-part level ---------- */
626 reading_multipart_level = TRUE;
628 if (is_first_part) /* start with first part of new multi-part level */
630 /* copy level info structure from first part */
631 multipart_level = native_sp_level;
633 /* clear playfield of new multi-part level */
634 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
635 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
636 multipart_level.playfield[x][y] = fiSpace;
639 if (name_first == '?')
641 if (name_last == '?')
644 multipart_xpos = (int)(name_first - '0');
645 multipart_ypos = (int)(name_last - '0');
648 printf("----------> part (%d/%d) of multi-part level '%s'\n",
649 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
652 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
653 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
655 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
660 multipart_level.width = MAX(multipart_level.width,
661 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
662 multipart_level.height = MAX(multipart_level.height,
663 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
665 /* copy level part at the right position of multi-part level */
666 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
668 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
670 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
671 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
673 multipart_level.playfield[start_x + x][start_y + y] =
674 native_sp_level.playfield[x][y];
683 setLevelInfoToDefaults_SP();
685 Error(ERR_WARN, "single part of multi-part level -- using empty level");
688 if (reading_multipart_level)
689 native_sp_level = multipart_level;
691 copyInternalEngineVars_SP();
698 boolean LoadNativeLevel_SP(char *filename, int level_pos,
699 boolean level_info_only)
703 char name_first, name_last;
704 struct LevelInfo_SP multipart_level;
705 int multipart_xpos, multipart_ypos;
706 boolean is_multipart_level;
707 boolean is_first_part;
708 boolean reading_multipart_level = FALSE;
709 boolean use_empty_level = FALSE;
710 LevelInfoType *header = &native_sp_level.header;
711 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
712 strSuffixLower(filename, ".mpx"));
713 boolean demo_available = is_single_level_file;
714 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
715 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
716 int level_width = SP_STD_PLAYFIELD_WIDTH;
717 int level_height = SP_STD_PLAYFIELD_HEIGHT;
719 /* always start with reliable default values */
720 setLevelInfoToDefaults_SP();
721 copyInternalEngineVars_SP();
723 if (!(file = fopen(filename, MODE_READ)))
725 if (!level_info_only)
726 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
733 char mpx_chunk_name[4 + 1];
736 LevelDescriptor *mpx_level_desc;
738 getFileChunkBE(file, mpx_chunk_name, NULL);
740 if (!strEqual(mpx_chunk_name, "MPX "))
742 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
748 mpx_version = getFile16BitLE(file);
750 if (mpx_version != 1)
752 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
758 mpx_level_count = getFile16BitLE(file);
760 if (mpx_level_count < 1)
762 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
768 if (level_pos >= mpx_level_count)
770 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
776 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
778 for (i = 0; i < mpx_level_count; i++)
780 LevelDescriptor *ldesc = &mpx_level_desc[i];
782 ldesc->Width = getFile16BitLE(file);
783 ldesc->Height = getFile16BitLE(file);
784 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
785 ldesc->Size = getFile32BitLE(file);
788 level_width = mpx_level_desc[level_pos].Width;
789 level_height = mpx_level_desc[level_pos].Height;
791 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
794 /* position file stream to the requested level (in case of level package) */
795 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
797 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
802 /* there exist Supaplex level package files with multi-part levels which
803 can be detected as follows: instead of leading and trailing dashes ('-')
804 to pad the level name, they have leading and trailing numbers which are
805 the x and y coordinations of the current part of the multi-part level;
806 if there are '?' characters instead of numbers on the left or right side
807 of the level name, the multi-part level consists of only horizontal or
810 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
812 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
815 /* check if this level is a part of a bigger multi-part level */
817 if (is_single_level_file)
820 name_first = header->LevelTitle[0];
821 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
824 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
825 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
828 ((name_first == '?' || name_first == '1') &&
829 (name_last == '?' || name_last == '1'));
831 if (is_multipart_level)
833 /* correct leading multipart level meta information in level name */
835 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
837 header->LevelTitle[i] = '-';
839 /* correct trailing multipart level meta information in level name */
840 for (i = SP_LEVEL_NAME_LEN - 1;
841 i >= 0 && header->LevelTitle[i] == name_last;
843 header->LevelTitle[i] = '-';
846 /* ---------- check for normal single level ---------- */
848 if (!reading_multipart_level && !is_multipart_level)
850 /* the current level is simply a normal single-part level, and we are
851 not reading a multi-part level yet, so return the level as it is */
856 /* ---------- check for empty level (unused multi-part) ---------- */
858 if (!reading_multipart_level && is_multipart_level && !is_first_part)
860 /* this is a part of a multi-part level, but not the first part
861 (and we are not already reading parts of a multi-part level);
862 in this case, use an empty level instead of the single part */
864 use_empty_level = TRUE;
869 /* ---------- check for finished multi-part level ---------- */
871 if (reading_multipart_level &&
872 (!is_multipart_level ||
873 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
876 /* we are already reading parts of a multi-part level, but this level is
877 either not a multi-part level, or a part of a different multi-part
878 level; in both cases, the multi-part level seems to be complete */
883 /* ---------- here we have one part of a multi-part level ---------- */
885 reading_multipart_level = TRUE;
887 if (is_first_part) /* start with first part of new multi-part level */
889 /* copy level info structure from first part */
890 multipart_level = native_sp_level;
892 /* clear playfield of new multi-part level */
893 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
894 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
895 multipart_level.playfield[x][y] = fiSpace;
898 if (name_first == '?')
900 if (name_last == '?')
903 multipart_xpos = (int)(name_first - '0');
904 multipart_ypos = (int)(name_last - '0');
907 printf("----------> part (%d/%d) of multi-part level '%s'\n",
908 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
911 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
912 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
914 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
919 multipart_level.width = MAX(multipart_level.width,
920 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
921 multipart_level.height = MAX(multipart_level.height,
922 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
924 /* copy level part at the right position of multi-part level */
925 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
927 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
929 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
930 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
932 multipart_level.playfield[start_x + x][start_y + y] =
933 native_sp_level.playfield[x][y];
942 setLevelInfoToDefaults_SP();
944 Error(ERR_WARN, "single part of multi-part level -- using empty level");
947 if (reading_multipart_level)
948 native_sp_level = multipart_level;
950 copyInternalEngineVars_SP();
957 void SaveNativeLevel_SP(char *filename)
959 LevelInfoType *header = &native_sp_level.header;
963 if (!(file = fopen(filename, MODE_WRITE)))
965 Error(ERR_WARN, "cannot save native level file '%s'", filename);
970 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
971 for (y = 0; y < native_sp_level.height; y++)
972 for (x = 0; x < native_sp_level.width; x++)
973 putFile8Bit(file, native_sp_level.playfield[x][y]);
975 /* write level header (96 bytes) */
977 WriteUnusedBytesToFile(file, 4);
979 putFile8Bit(file, header->InitialGravity);
980 putFile8Bit(file, header->Version);
982 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
983 putFile8Bit(file, header->LevelTitle[i]);
985 putFile8Bit(file, header->InitialFreezeZonks);
986 putFile8Bit(file, header->InfotronsNeeded);
987 putFile8Bit(file, header->SpecialPortCount);
989 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
991 SpecialPortType *port = &header->SpecialPort[i];
993 putFile16BitBE(file, port->PortLocation);
994 putFile8Bit(file, port->Gravity);
995 putFile8Bit(file, port->FreezeZonks);
996 putFile8Bit(file, port->FreezeEnemies);
998 WriteUnusedBytesToFile(file, 1);
1001 putFile8Bit(file, header->SpeedByte);
1002 putFile8Bit(file, header->CheckSumByte);
1003 putFile16BitLE(file, header->DemoRandomSeed);
1005 /* also save demo tape, if available */
1007 if (native_sp_level.demo.is_available)
1009 putFile8Bit(file, native_sp_level.demo.level_nr);
1011 for (i = 0; i < native_sp_level.demo.length; i++)
1012 putFile8Bit(file, native_sp_level.demo.data[i]);