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_PLAYFIELD_WIDTH;
17 native_sp_level.height = SP_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 95 89 95 89 3b 8a 3b 8a 3b 8a", // |..........;.;.;.|
59 "3b 8a 3b 8a 3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a", // |;.;.;.;.;.è.è.è.|
60 "e8 8a e8 8a e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b", // |è.è.è.è.è.±.±.±.|
61 "b1 8b b1 8b b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c", // |±.±.±.±.±.......|
62 "85 8c 85 8c 85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d", // |..........[.[.[.|
63 "5b 8d 5b 8d 5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e", // |[.[.[.[.[.......|
64 "06 8e 06 8e 06 8e 06 8e 06 8e ac 8e ac 8e ac 8e", // |..........¬.¬.¬.|
65 "ac 8e ac 8e ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f", // |¬.¬.¬.¬.¬.Y.Y.Y.|
66 "59 8f 59 8f 59 8f 59 8f 59 8f 00 00 70 13 00 00", // |Y.Y.Y.Y.Y...p...|
67 "00 00 e8 17 00 00 00 00 00 00 69 38 00 00 00 00", // |..è.......i8....|
68 "00 00 00 00 00 00 00 00 00 00 00 00 d0 86 00 00", // |............Ð...|
69 "b2 34 00 00 00 00 00 00 00 00 8f 8b 1d 34 00 00", // |²4...........4..|
70 "00 00 00 00 00 00 00 00 23 39 09 09 00 0c 00 08", // |........#9......|
71 "00 58 00 00 00 00 00 25 77 06 7f 00 00 00 01 00", // |.X.....%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 00 00 00 00 ec 06 26", // |.............ì.&|
81 "05 00 00 00 00 00 00 01 00 00 00 00 31 32 33 34", // |............1234|
82 "35 36 37 38 39 30 2d 00 08 00 51 57 45 52 54 59", // |567890-...QWERTY|
83 "55 49 4f 50 00 00 0a 00 41 53 44 46 47 48 4a 4b", // |UIOP....ASDFGHJK|
84 "4c 00 00 00 00 00 5a 58 43 56 42 4e 4d 00 00 00", // |L.....ZXCVBNM...|
85 "00 00 00 20 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 00 00 00 00 2e 00 1e 00 31 00", // |..............1.|
92 "14 00 39 00 1f 00 14 00 18 00 ff ff 01 00 01 4c", // |..9.......ÿÿ...L|
93 "45 56 45 4c 53 2e 44 41 54 00 00 00 00 00 00 00", // |EVELS.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", // |................|
101 "00 00 00 00", // |.... |
108 LInfo = native_sp_level.header;
110 FieldWidth = native_sp_level.width;
111 FieldHeight = native_sp_level.height;
114 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
115 LevelMax = (FieldWidth * FieldHeight) - 1;
117 FileMax = FieldMax + native_sp_level.demo.length;
119 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
120 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
122 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
124 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth * 2, FieldMax);
130 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
131 for (i = -FieldWidth * 2; i < -FieldWidth; i++)
132 PlayField16[i] = 0x20;
136 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
137 for (i = -FieldWidth; i < 0; i++)
138 PlayField16[i] = 0x20;
142 for (y = 0; y < native_sp_level.height; y++)
143 for (x = 0; x < native_sp_level.width; x++)
144 PlayField8[count++] = native_sp_level.playfield[x][y];
146 /* add raw header bytes to subsequent playfield buffer zone */
147 for (i = 0; i < SP_HEADER_SIZE; i++)
148 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
150 for (i = 0; i < count; i++)
152 PlayField16[i] = PlayField8[i];
153 DisPlayField[i] = PlayField8[i];
159 for (i = 0; y = 0; y < native_sp_level.height; y++)
161 for (x = 0; x < native_sp_level.width; x++)
163 PlayField8[i] = native_sp_level.playfield[x][y];
165 PlayField16[i] = PlayField8[i];
166 DisPlayField[i] = PlayField8[i];
175 if (native_sp_level.demo.is_available)
177 DemoAvailable = True;
179 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
181 for (i = 0; i < native_sp_level.demo.length; i++)
182 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
185 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
186 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
187 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
189 DemoPointer = FieldMax + 1;
190 DemoOffset = DemoPointer;
191 DemoKeyRepeatCounter = 0;
193 GravityFlag = LInfo.InitialGravity;
194 FreezeZonks = LInfo.InitialFreezeZonks;
197 /* this is set by main game tape code to native random generator directly */
201 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
204 RandomSeed = LInfo.DemoRandomSeed;
211 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
212 boolean demo_available)
214 LevelInfoType *header = &native_sp_level.header;
217 /* for details of the Supaplex level format, see Herman Perk's Supaplex
218 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
220 native_sp_level.width = width;
221 native_sp_level.height = height;
223 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
224 for (y = 0; y < native_sp_level.height; y++)
225 for (x = 0; x < native_sp_level.width; x++)
226 native_sp_level.playfield[x][y] = getFile8Bit(file);
228 /* read level header (96 bytes) */
230 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
232 /* initial gravity: 1 == "on", anything else (0) == "off" */
233 header->InitialGravity = getFile8Bit(file);
235 /* SpeedFixVersion XOR 0x20 */
236 header->Version = getFile8Bit(file);
238 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
239 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
240 header->LevelTitle[i] = getFile8Bit(file);
242 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
243 header->InitialFreezeZonks = getFile8Bit(file);
245 /* number of infotrons needed; 0 means that Supaplex will count the total
246 amount of infotrons in the level and use the low byte of that number
247 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
248 header->InfotronsNeeded = getFile8Bit(file);
250 /* number of special ("gravity") port entries below (maximum 10 allowed) */
251 header->SpecialPortCount = getFile8Bit(file);
254 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
257 /* database of properties of up to 10 special ports (6 bytes per port) */
258 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
260 SpecialPortType *port = &header->SpecialPort[i];
262 /* high and low byte of the location of a special port; if (x, y) are the
263 coordinates of a port in the field and (0, 0) is the top-left corner,
264 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
265 of what may be expected: Supaplex works with a game field in memory
266 which is 2 bytes per tile) */
267 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
271 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
272 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
274 printf("::: %d: port_location == %d => (%d, %d)\n",
275 i, port->PortLocation, port_x, port_y);
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 */
299 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
302 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
305 /* auto-determine number of infotrons if it was stored as "0" -- see above */
306 if (header->InfotronsNeeded == 0)
308 for (x = 0; x < native_sp_level.width; x++)
309 for (y = 0; y < native_sp_level.height; y++)
310 if (native_sp_level.playfield[x][y] == fiInfotron)
311 header->InfotronsNeeded++;
313 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
316 /* read raw level header bytes (96 bytes) */
318 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
319 for (i = 0; i < SP_HEADER_SIZE; i++)
320 native_sp_level.header_raw_bytes[i] = fgetc(file);
322 /* also load demo tape, if available (only in single level files) */
326 int level_nr = getFile8Bit(file);
328 level_nr &= 0x7f; /* clear highest bit */
329 level_nr = (level_nr < 1 ? 1 :
330 level_nr > 111 ? 111 : level_nr);
332 native_sp_level.demo.level_nr = level_nr;
334 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
336 native_sp_level.demo.data[i] = getFile8Bit(file);
338 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
346 native_sp_level.demo.length = i;
347 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
351 boolean LoadNativeLevel_SP(char *filename, int level_pos)
355 char name_first, name_last;
356 struct LevelInfo_SP multipart_level;
357 int multipart_xpos, multipart_ypos;
358 boolean is_multipart_level;
359 boolean is_first_part;
360 boolean reading_multipart_level = FALSE;
361 boolean use_empty_level = FALSE;
362 LevelInfoType *header = &native_sp_level.header;
363 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
364 strSuffixLower(filename, ".mpx"));
365 boolean demo_available = is_single_level_file;
366 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
367 int file_seek_pos = level_pos * SP_LEVEL_SIZE;
368 int level_width = SP_PLAYFIELD_WIDTH;
369 int level_height = SP_PLAYFIELD_HEIGHT;
371 /* always start with reliable default values */
372 setLevelInfoToDefaults_SP();
373 copyInternalEngineVars_SP();
375 if (!(file = fopen(filename, MODE_READ)))
377 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
384 char mpx_chunk_name[4 + 1];
387 LevelDescriptor *mpx_level_desc;
389 getFileChunkBE(file, mpx_chunk_name, NULL);
391 if (!strEqual(mpx_chunk_name, "MPX "))
393 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
399 mpx_version = getFile16BitLE(file);
401 if (mpx_version != 1)
403 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
409 mpx_level_count = getFile16BitLE(file);
411 if (mpx_level_count < 1)
413 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
419 if (level_pos >= mpx_level_count)
421 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
427 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
429 for (i = 0; i < mpx_level_count; i++)
431 LevelDescriptor *ldesc = &mpx_level_desc[i];
433 ldesc->Width = getFile16BitLE(file);
434 ldesc->Height = getFile16BitLE(file);
435 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
436 ldesc->Size = getFile32BitLE(file);
439 level_width = mpx_level_desc[level_pos].Width;
440 level_height = mpx_level_desc[level_pos].Height;
442 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
445 /* position file stream to the requested level (in case of level package) */
446 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
448 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
453 /* there exist Supaplex level package files with multi-part levels which
454 can be detected as follows: instead of leading and trailing dashes ('-')
455 to pad the level name, they have leading and trailing numbers which are
456 the x and y coordinations of the current part of the multi-part level;
457 if there are '?' characters instead of numbers on the left or right side
458 of the level name, the multi-part level consists of only horizontal or
461 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
463 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
466 /* check if this level is a part of a bigger multi-part level */
468 if (is_single_level_file)
471 name_first = header->LevelTitle[0];
472 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
475 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
476 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
479 ((name_first == '?' || name_first == '1') &&
480 (name_last == '?' || name_last == '1'));
482 if (is_multipart_level)
484 /* correct leading multipart level meta information in level name */
486 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
488 header->LevelTitle[i] = '-';
490 /* correct trailing multipart level meta information in level name */
491 for (i = SP_LEVEL_NAME_LEN - 1;
492 i >= 0 && header->LevelTitle[i] == name_last;
494 header->LevelTitle[i] = '-';
497 /* ---------- check for normal single level ---------- */
499 if (!reading_multipart_level && !is_multipart_level)
501 /* the current level is simply a normal single-part level, and we are
502 not reading a multi-part level yet, so return the level as it is */
507 /* ---------- check for empty level (unused multi-part) ---------- */
509 if (!reading_multipart_level && is_multipart_level && !is_first_part)
511 /* this is a part of a multi-part level, but not the first part
512 (and we are not already reading parts of a multi-part level);
513 in this case, use an empty level instead of the single part */
515 use_empty_level = TRUE;
520 /* ---------- check for finished multi-part level ---------- */
522 if (reading_multipart_level &&
523 (!is_multipart_level ||
524 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
527 /* we are already reading parts of a multi-part level, but this level is
528 either not a multi-part level, or a part of a different multi-part
529 level; in both cases, the multi-part level seems to be complete */
534 /* ---------- here we have one part of a multi-part level ---------- */
536 reading_multipart_level = TRUE;
538 if (is_first_part) /* start with first part of new multi-part level */
540 /* copy level info structure from first part */
541 multipart_level = native_sp_level;
543 /* clear playfield of new multi-part level */
544 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
545 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
546 multipart_level.playfield[x][y] = fiSpace;
549 if (name_first == '?')
551 if (name_last == '?')
554 multipart_xpos = (int)(name_first - '0');
555 multipart_ypos = (int)(name_last - '0');
558 printf("----------> part (%d/%d) of multi-part level '%s'\n",
559 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
562 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
563 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
565 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
570 multipart_level.width = MAX(multipart_level.width,
571 multipart_xpos * SP_PLAYFIELD_WIDTH);
572 multipart_level.height = MAX(multipart_level.height,
573 multipart_ypos * SP_PLAYFIELD_HEIGHT);
575 /* copy level part at the right position of multi-part level */
576 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
578 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
580 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
581 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
583 multipart_level.playfield[start_x + x][start_y + y] =
584 native_sp_level.playfield[x][y];
593 setLevelInfoToDefaults_SP();
595 Error(ERR_WARN, "single part of multi-part level -- using empty level");
598 if (reading_multipart_level)
599 native_sp_level = multipart_level;
601 copyInternalEngineVars_SP();