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;
171 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
172 boolean demo_available)
174 LevelInfoType *header = &native_sp_level.header;
177 /* for details of the Supaplex level format, see Herman Perk's Supaplex
178 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
180 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
181 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
183 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
184 /* (MPX levels may have non-standard playfield size -- check max. size) */
185 for (y = 0; y < height; y++)
187 for (x = 0; x < width; x++)
189 byte element = getFile8Bit(file);
191 if (x < SP_MAX_PLAYFIELD_WIDTH &&
192 y < SP_MAX_PLAYFIELD_HEIGHT)
193 native_sp_level.playfield[x][y] = element;
197 /* read level header (96 bytes) */
199 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
201 /* initial gravity: 1 == "on", anything else (0) == "off" */
202 header->InitialGravity = getFile8Bit(file);
204 /* SpeedFixVersion XOR 0x20 */
205 header->Version = getFile8Bit(file);
207 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
208 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
209 header->LevelTitle[i] = getFile8Bit(file);
211 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
212 header->InitialFreezeZonks = getFile8Bit(file);
214 /* number of infotrons needed; 0 means that Supaplex will count the total
215 amount of infotrons in the level and use the low byte of that number
216 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
217 header->InfotronsNeeded = getFile8Bit(file);
219 /* number of special ("gravity") port entries below (maximum 10 allowed) */
220 header->SpecialPortCount = getFile8Bit(file);
222 /* database of properties of up to 10 special ports (6 bytes per port) */
223 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
225 SpecialPortType *port = &header->SpecialPort[i];
227 /* high and low byte of the location of a special port; if (x, y) are the
228 coordinates of a port in the field and (0, 0) is the top-left corner,
229 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
230 of what may be expected: Supaplex works with a game field in memory
231 which is 2 bytes per tile) */
232 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
234 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
235 port->Gravity = getFile8Bit(file);
237 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
238 port->FreezeZonks = getFile8Bit(file);
240 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
241 port->FreezeEnemies = getFile8Bit(file);
243 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
246 /* SpeedByte XOR Highbyte(RandomSeed) */
247 header->SpeedByte = getFile8Bit(file);
249 /* CheckSum XOR SpeedByte */
250 header->CheckSumByte = getFile8Bit(file);
252 /* random seed used for recorded demos */
253 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
255 /* auto-determine number of infotrons if it was stored as "0" -- see above */
256 if (header->InfotronsNeeded == 0)
258 for (x = 0; x < native_sp_level.width; x++)
259 for (y = 0; y < native_sp_level.height; y++)
260 if (native_sp_level.playfield[x][y] == fiInfotron)
261 header->InfotronsNeeded++;
263 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
266 /* read raw level header bytes (96 bytes) */
268 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
269 for (i = 0; i < SP_HEADER_SIZE; i++)
270 native_sp_level.header_raw_bytes[i] = fgetc(file);
272 /* also load demo tape, if available (only in single level files) */
276 int level_nr = getFile8Bit(file);
278 level_nr &= 0x7f; /* clear highest bit */
279 level_nr = (level_nr < 1 ? 1 :
280 level_nr > 111 ? 111 : level_nr);
282 native_sp_level.demo.level_nr = level_nr;
284 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
286 native_sp_level.demo.data[i] = getFile8Bit(file);
288 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
296 native_sp_level.demo.length = i;
297 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
301 boolean LoadNativeLevel_SP(char *filename, int level_pos)
305 char name_first, name_last;
306 struct LevelInfo_SP multipart_level;
307 int multipart_xpos, multipart_ypos;
308 boolean is_multipart_level;
309 boolean is_first_part;
310 boolean reading_multipart_level = FALSE;
311 boolean use_empty_level = FALSE;
312 LevelInfoType *header = &native_sp_level.header;
313 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
314 strSuffixLower(filename, ".mpx"));
315 boolean demo_available = is_single_level_file;
316 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
317 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
318 int level_width = SP_STD_PLAYFIELD_WIDTH;
319 int level_height = SP_STD_PLAYFIELD_HEIGHT;
321 /* always start with reliable default values */
322 setLevelInfoToDefaults_SP();
323 copyInternalEngineVars_SP();
325 if (!(file = fopen(filename, MODE_READ)))
327 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
334 char mpx_chunk_name[4 + 1];
337 LevelDescriptor *mpx_level_desc;
339 getFileChunkBE(file, mpx_chunk_name, NULL);
341 if (!strEqual(mpx_chunk_name, "MPX "))
343 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
349 mpx_version = getFile16BitLE(file);
351 if (mpx_version != 1)
353 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
359 mpx_level_count = getFile16BitLE(file);
361 if (mpx_level_count < 1)
363 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
369 if (level_pos >= mpx_level_count)
371 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
377 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
379 for (i = 0; i < mpx_level_count; i++)
381 LevelDescriptor *ldesc = &mpx_level_desc[i];
383 ldesc->Width = getFile16BitLE(file);
384 ldesc->Height = getFile16BitLE(file);
385 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
386 ldesc->Size = getFile32BitLE(file);
389 level_width = mpx_level_desc[level_pos].Width;
390 level_height = mpx_level_desc[level_pos].Height;
392 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
395 /* position file stream to the requested level (in case of level package) */
396 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
398 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
403 /* there exist Supaplex level package files with multi-part levels which
404 can be detected as follows: instead of leading and trailing dashes ('-')
405 to pad the level name, they have leading and trailing numbers which are
406 the x and y coordinations of the current part of the multi-part level;
407 if there are '?' characters instead of numbers on the left or right side
408 of the level name, the multi-part level consists of only horizontal or
411 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
413 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
416 /* check if this level is a part of a bigger multi-part level */
418 if (is_single_level_file)
421 name_first = header->LevelTitle[0];
422 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
425 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
426 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
429 ((name_first == '?' || name_first == '1') &&
430 (name_last == '?' || name_last == '1'));
432 if (is_multipart_level)
434 /* correct leading multipart level meta information in level name */
436 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
438 header->LevelTitle[i] = '-';
440 /* correct trailing multipart level meta information in level name */
441 for (i = SP_LEVEL_NAME_LEN - 1;
442 i >= 0 && header->LevelTitle[i] == name_last;
444 header->LevelTitle[i] = '-';
447 /* ---------- check for normal single level ---------- */
449 if (!reading_multipart_level && !is_multipart_level)
451 /* the current level is simply a normal single-part level, and we are
452 not reading a multi-part level yet, so return the level as it is */
457 /* ---------- check for empty level (unused multi-part) ---------- */
459 if (!reading_multipart_level && is_multipart_level && !is_first_part)
461 /* this is a part of a multi-part level, but not the first part
462 (and we are not already reading parts of a multi-part level);
463 in this case, use an empty level instead of the single part */
465 use_empty_level = TRUE;
470 /* ---------- check for finished multi-part level ---------- */
472 if (reading_multipart_level &&
473 (!is_multipart_level ||
474 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
477 /* we are already reading parts of a multi-part level, but this level is
478 either not a multi-part level, or a part of a different multi-part
479 level; in both cases, the multi-part level seems to be complete */
484 /* ---------- here we have one part of a multi-part level ---------- */
486 reading_multipart_level = TRUE;
488 if (is_first_part) /* start with first part of new multi-part level */
490 /* copy level info structure from first part */
491 multipart_level = native_sp_level;
493 /* clear playfield of new multi-part level */
494 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
495 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
496 multipart_level.playfield[x][y] = fiSpace;
499 if (name_first == '?')
501 if (name_last == '?')
504 multipart_xpos = (int)(name_first - '0');
505 multipart_ypos = (int)(name_last - '0');
508 printf("----------> part (%d/%d) of multi-part level '%s'\n",
509 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
512 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
513 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
515 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
520 multipart_level.width = MAX(multipart_level.width,
521 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
522 multipart_level.height = MAX(multipart_level.height,
523 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
525 /* copy level part at the right position of multi-part level */
526 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
528 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
530 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
531 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
533 multipart_level.playfield[start_x + x][start_y + y] =
534 native_sp_level.playfield[x][y];
543 setLevelInfoToDefaults_SP();
545 Error(ERR_WARN, "single part of multi-part level -- using empty level");
548 if (reading_multipart_level)
549 native_sp_level = multipart_level;
551 copyInternalEngineVars_SP();
556 void SaveNativeLevel_SP(char *filename)
558 LevelInfoType *header = &native_sp_level.header;
562 if (!(file = fopen(filename, MODE_WRITE)))
564 Error(ERR_WARN, "cannot save native level file '%s'", filename);
569 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
570 for (y = 0; y < native_sp_level.height; y++)
571 for (x = 0; x < native_sp_level.width; x++)
572 putFile8Bit(file, native_sp_level.playfield[x][y]);
574 /* write level header (96 bytes) */
576 WriteUnusedBytesToFile(file, 4);
578 putFile8Bit(file, header->InitialGravity);
579 putFile8Bit(file, header->Version);
581 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
582 putFile8Bit(file, header->LevelTitle[i]);
584 putFile8Bit(file, header->InitialFreezeZonks);
585 putFile8Bit(file, header->InfotronsNeeded);
586 putFile8Bit(file, header->SpecialPortCount);
588 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
590 SpecialPortType *port = &header->SpecialPort[i];
592 putFile16BitBE(file, port->PortLocation);
593 putFile8Bit(file, port->Gravity);
594 putFile8Bit(file, port->FreezeZonks);
595 putFile8Bit(file, port->FreezeEnemies);
597 WriteUnusedBytesToFile(file, 1);
600 putFile8Bit(file, header->SpeedByte);
601 putFile8Bit(file, header->CheckSumByte);
602 putFile16BitLE(file, header->DemoRandomSeed);
604 /* also save demo tape, if available */
606 if (native_sp_level.demo.is_available)
608 putFile8Bit(file, native_sp_level.demo.level_nr);
610 for (i = 0; i < native_sp_level.demo.length; i++)
611 putFile8Bit(file, native_sp_level.demo.data[i]);