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,
302 boolean level_info_only)
306 char name_first, name_last;
307 struct LevelInfo_SP multipart_level;
308 int multipart_xpos, multipart_ypos;
309 boolean is_multipart_level;
310 boolean is_first_part;
311 boolean reading_multipart_level = FALSE;
312 boolean use_empty_level = FALSE;
313 LevelInfoType *header = &native_sp_level.header;
314 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
315 strSuffixLower(filename, ".mpx"));
316 boolean demo_available = is_single_level_file;
317 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
318 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
319 int level_width = SP_STD_PLAYFIELD_WIDTH;
320 int level_height = SP_STD_PLAYFIELD_HEIGHT;
322 /* always start with reliable default values */
323 setLevelInfoToDefaults_SP();
324 copyInternalEngineVars_SP();
326 if (!(file = fopen(filename, MODE_READ)))
328 if (!level_info_only)
329 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
336 char mpx_chunk_name[4 + 1];
339 LevelDescriptor *mpx_level_desc;
341 getFileChunkBE(file, mpx_chunk_name, NULL);
343 if (!strEqual(mpx_chunk_name, "MPX "))
345 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
351 mpx_version = getFile16BitLE(file);
353 if (mpx_version != 1)
355 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
361 mpx_level_count = getFile16BitLE(file);
363 if (mpx_level_count < 1)
365 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
371 if (level_pos >= mpx_level_count)
373 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
379 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
381 for (i = 0; i < mpx_level_count; i++)
383 LevelDescriptor *ldesc = &mpx_level_desc[i];
385 ldesc->Width = getFile16BitLE(file);
386 ldesc->Height = getFile16BitLE(file);
387 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
388 ldesc->Size = getFile32BitLE(file);
391 level_width = mpx_level_desc[level_pos].Width;
392 level_height = mpx_level_desc[level_pos].Height;
394 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
397 /* position file stream to the requested level (in case of level package) */
398 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
400 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
405 /* there exist Supaplex level package files with multi-part levels which
406 can be detected as follows: instead of leading and trailing dashes ('-')
407 to pad the level name, they have leading and trailing numbers which are
408 the x and y coordinations of the current part of the multi-part level;
409 if there are '?' characters instead of numbers on the left or right side
410 of the level name, the multi-part level consists of only horizontal or
413 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
415 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
418 /* check if this level is a part of a bigger multi-part level */
420 if (is_single_level_file)
423 name_first = header->LevelTitle[0];
424 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
427 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
428 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
431 ((name_first == '?' || name_first == '1') &&
432 (name_last == '?' || name_last == '1'));
434 if (is_multipart_level)
436 /* correct leading multipart level meta information in level name */
438 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
440 header->LevelTitle[i] = '-';
442 /* correct trailing multipart level meta information in level name */
443 for (i = SP_LEVEL_NAME_LEN - 1;
444 i >= 0 && header->LevelTitle[i] == name_last;
446 header->LevelTitle[i] = '-';
449 /* ---------- check for normal single level ---------- */
451 if (!reading_multipart_level && !is_multipart_level)
453 /* the current level is simply a normal single-part level, and we are
454 not reading a multi-part level yet, so return the level as it is */
459 /* ---------- check for empty level (unused multi-part) ---------- */
461 if (!reading_multipart_level && is_multipart_level && !is_first_part)
463 /* this is a part of a multi-part level, but not the first part
464 (and we are not already reading parts of a multi-part level);
465 in this case, use an empty level instead of the single part */
467 use_empty_level = TRUE;
472 /* ---------- check for finished multi-part level ---------- */
474 if (reading_multipart_level &&
475 (!is_multipart_level ||
476 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
479 /* we are already reading parts of a multi-part level, but this level is
480 either not a multi-part level, or a part of a different multi-part
481 level; in both cases, the multi-part level seems to be complete */
486 /* ---------- here we have one part of a multi-part level ---------- */
488 reading_multipart_level = TRUE;
490 if (is_first_part) /* start with first part of new multi-part level */
492 /* copy level info structure from first part */
493 multipart_level = native_sp_level;
495 /* clear playfield of new multi-part level */
496 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
497 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
498 multipart_level.playfield[x][y] = fiSpace;
501 if (name_first == '?')
503 if (name_last == '?')
506 multipart_xpos = (int)(name_first - '0');
507 multipart_ypos = (int)(name_last - '0');
510 printf("----------> part (%d/%d) of multi-part level '%s'\n",
511 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
514 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
515 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
517 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
522 multipart_level.width = MAX(multipart_level.width,
523 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
524 multipart_level.height = MAX(multipart_level.height,
525 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
527 /* copy level part at the right position of multi-part level */
528 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
530 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
532 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
533 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
535 multipart_level.playfield[start_x + x][start_y + y] =
536 native_sp_level.playfield[x][y];
545 setLevelInfoToDefaults_SP();
547 Error(ERR_WARN, "single part of multi-part level -- using empty level");
550 if (reading_multipart_level)
551 native_sp_level = multipart_level;
553 copyInternalEngineVars_SP();
558 void SaveNativeLevel_SP(char *filename)
560 LevelInfoType *header = &native_sp_level.header;
564 if (!(file = fopen(filename, MODE_WRITE)))
566 Error(ERR_WARN, "cannot save native level file '%s'", filename);
571 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
572 for (y = 0; y < native_sp_level.height; y++)
573 for (x = 0; x < native_sp_level.width; x++)
574 putFile8Bit(file, native_sp_level.playfield[x][y]);
576 /* write level header (96 bytes) */
578 WriteUnusedBytesToFile(file, 4);
580 putFile8Bit(file, header->InitialGravity);
581 putFile8Bit(file, header->Version);
583 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
584 putFile8Bit(file, header->LevelTitle[i]);
586 putFile8Bit(file, header->InitialFreezeZonks);
587 putFile8Bit(file, header->InfotronsNeeded);
588 putFile8Bit(file, header->SpecialPortCount);
590 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
592 SpecialPortType *port = &header->SpecialPort[i];
594 putFile16BitBE(file, port->PortLocation);
595 putFile8Bit(file, port->Gravity);
596 putFile8Bit(file, port->FreezeZonks);
597 putFile8Bit(file, port->FreezeEnemies);
599 WriteUnusedBytesToFile(file, 1);
602 putFile8Bit(file, header->SpeedByte);
603 putFile8Bit(file, header->CheckSumByte);
604 putFile16BitLE(file, header->DemoRandomSeed);
606 /* also save demo tape, if available */
608 if (native_sp_level.demo.is_available)
610 putFile8Bit(file, native_sp_level.demo.level_nr);
612 for (i = 0; i < native_sp_level.demo.length; i++)
613 putFile8Bit(file, native_sp_level.demo.data[i]);