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;
75 /* initialize preceding playfield buffer */
76 for (i = -game_sp.preceding_buffer_size; i < 0; i++)
79 /* initialize preceding playfield buffer */
80 for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++)
84 for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
86 char *s = game_sp.preceding_buffer[i];
87 boolean hi_byte = FALSE; /* little endian data => start with low byte */
89 while (s[0] != '\0' && s[1] != '\0')
91 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
92 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
93 int byte = (hi_nibble << 4) | lo_nibble;
98 PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
113 for (y = 0; y < native_sp_level.height; y++)
114 for (x = 0; x < native_sp_level.width; x++)
115 PlayField8[count++] = native_sp_level.playfield[x][y];
117 /* add raw header bytes to subsequent playfield buffer zone */
118 for (i = 0; i < SP_HEADER_SIZE; i++)
119 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
121 for (i = 0; i < count; i++)
123 PlayField16[i] = PlayField8[i];
124 DisPlayField[i] = PlayField8[i];
128 if (native_sp_level.demo.is_available)
129 DemoAvailable = True;
131 GravityFlag = LInfo.InitialGravity;
132 FreezeZonks = LInfo.InitialFreezeZonks;
136 /* random seed set by main game tape code to native random generator seed */
139 static void LoadNativeLevelFromFileStream_SP(File *file, int width, int height,
140 boolean demo_available)
142 LevelInfoType *header = &native_sp_level.header;
145 /* for details of the Supaplex level format, see Herman Perk's Supaplex
146 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
148 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
149 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
151 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
152 /* (MPX levels may have non-standard playfield size -- check max. size) */
153 for (y = 0; y < height; y++)
155 for (x = 0; x < width; x++)
157 byte element = getFile8Bit(file);
159 if (x < SP_MAX_PLAYFIELD_WIDTH &&
160 y < SP_MAX_PLAYFIELD_HEIGHT)
161 native_sp_level.playfield[x][y] = element;
165 /* read level header (96 bytes) */
167 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
169 /* initial gravity: 1 == "on", anything else (0) == "off" */
170 header->InitialGravity = getFile8Bit(file);
172 /* SpeedFixVersion XOR 0x20 */
173 header->Version = getFile8Bit(file);
175 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
176 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
177 header->LevelTitle[i] = getFile8Bit(file);
179 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
180 header->InitialFreezeZonks = getFile8Bit(file);
182 /* number of infotrons needed; 0 means that Supaplex will count the total
183 amount of infotrons in the level and use the low byte of that number
184 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
185 header->InfotronsNeeded = getFile8Bit(file);
187 /* number of special ("gravity") port entries below (maximum 10 allowed) */
188 header->SpecialPortCount = getFile8Bit(file);
190 /* database of properties of up to 10 special ports (6 bytes per port) */
191 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
193 SpecialPortType *port = &header->SpecialPort[i];
195 /* high and low byte of the location of a special port; if (x, y) are the
196 coordinates of a port in the field and (0, 0) is the top-left corner,
197 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
198 of what may be expected: Supaplex works with a game field in memory
199 which is 2 bytes per tile) */
200 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
202 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
203 port->Gravity = getFile8Bit(file);
205 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
206 port->FreezeZonks = getFile8Bit(file);
208 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
209 port->FreezeEnemies = getFile8Bit(file);
211 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
214 /* SpeedByte XOR Highbyte(RandomSeed) */
215 header->SpeedByte = getFile8Bit(file);
217 /* CheckSum XOR SpeedByte */
218 header->CheckSumByte = getFile8Bit(file);
220 /* random seed used for recorded demos */
221 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
223 /* auto-determine number of infotrons if it was stored as "0" -- see above */
224 if (header->InfotronsNeeded == 0)
226 for (x = 0; x < native_sp_level.width; x++)
227 for (y = 0; y < native_sp_level.height; y++)
228 if (native_sp_level.playfield[x][y] == fiInfotron)
229 header->InfotronsNeeded++;
231 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
234 /* read raw level header bytes (96 bytes) */
236 seekFile(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
237 for (i = 0; i < SP_HEADER_SIZE; i++)
238 native_sp_level.header_raw_bytes[i] = getByteFromFile(file);
240 /* also load demo tape, if available (only in single level files) */
244 int level_nr = getFile8Bit(file);
246 level_nr &= 0x7f; /* clear highest bit */
247 level_nr = (level_nr < 1 ? 1 :
248 level_nr > 111 ? 111 : level_nr);
250 native_sp_level.demo.level_nr = level_nr;
252 for (i = 0; i < SP_MAX_TAPE_LEN && !checkEndOfFile(file); i++)
254 native_sp_level.demo.data[i] = getFile8Bit(file);
256 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
260 if (i >= SP_MAX_TAPE_LEN)
261 Error(ERR_WARN, "SP demo truncated: size exceeds maximum SP demo size %d",
264 native_sp_level.demo.length = i;
265 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
269 boolean LoadNativeLevel_SP(char *filename, int level_pos,
270 boolean level_info_only)
274 char name_first, name_last;
275 struct LevelInfo_SP multipart_level;
276 int multipart_xpos, multipart_ypos;
277 boolean is_multipart_level;
278 boolean is_first_part;
279 boolean reading_multipart_level = FALSE;
280 boolean use_empty_level = FALSE;
281 LevelInfoType *header = &native_sp_level.header;
282 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
283 strSuffixLower(filename, ".mpx"));
284 boolean demo_available = is_single_level_file;
285 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
286 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
287 int level_width = SP_STD_PLAYFIELD_WIDTH;
288 int level_height = SP_STD_PLAYFIELD_HEIGHT;
290 /* always start with reliable default values */
291 setLevelInfoToDefaults_SP();
292 copyInternalEngineVars_SP();
294 if (!(file = openFile(filename, MODE_READ)))
296 if (!level_info_only)
297 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
304 char mpx_chunk_name[4 + 1];
307 LevelDescriptor *mpx_level_desc;
309 getFileChunkBE(file, mpx_chunk_name, NULL);
311 if (!strEqual(mpx_chunk_name, "MPX "))
313 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
319 mpx_version = getFile16BitLE(file);
321 if (mpx_version != 1)
323 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
329 mpx_level_count = getFile16BitLE(file);
331 if (mpx_level_count < 1)
333 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
339 if (level_pos >= mpx_level_count)
341 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
347 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
349 for (i = 0; i < mpx_level_count; i++)
351 LevelDescriptor *ldesc = &mpx_level_desc[i];
353 ldesc->Width = getFile16BitLE(file);
354 ldesc->Height = getFile16BitLE(file);
355 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
356 ldesc->Size = getFile32BitLE(file);
359 level_width = mpx_level_desc[level_pos].Width;
360 level_height = mpx_level_desc[level_pos].Height;
362 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
365 /* position file stream to the requested level (in case of level package) */
366 if (seekFile(file, file_seek_pos, SEEK_SET) != 0)
368 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
373 /* there exist Supaplex level package files with multi-part levels which
374 can be detected as follows: instead of leading and trailing dashes ('-')
375 to pad the level name, they have leading and trailing numbers which are
376 the x and y coordinations of the current part of the multi-part level;
377 if there are '?' characters instead of numbers on the left or right side
378 of the level name, the multi-part level consists of only horizontal or
381 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
383 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
386 /* check if this level is a part of a bigger multi-part level */
388 if (is_single_level_file)
391 name_first = header->LevelTitle[0];
392 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
395 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
396 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
399 ((name_first == '?' || name_first == '1') &&
400 (name_last == '?' || name_last == '1'));
402 if (is_multipart_level)
404 /* correct leading multipart level meta information in level name */
406 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
408 header->LevelTitle[i] = '-';
410 /* correct trailing multipart level meta information in level name */
411 for (i = SP_LEVEL_NAME_LEN - 1;
412 i >= 0 && header->LevelTitle[i] == name_last;
414 header->LevelTitle[i] = '-';
417 /* ---------- check for normal single level ---------- */
419 if (!reading_multipart_level && !is_multipart_level)
421 /* the current level is simply a normal single-part level, and we are
422 not reading a multi-part level yet, so return the level as it is */
427 /* ---------- check for empty level (unused multi-part) ---------- */
429 if (!reading_multipart_level && is_multipart_level && !is_first_part)
431 /* this is a part of a multi-part level, but not the first part
432 (and we are not already reading parts of a multi-part level);
433 in this case, use an empty level instead of the single part */
435 use_empty_level = TRUE;
440 /* ---------- check for finished multi-part level ---------- */
442 if (reading_multipart_level &&
443 (!is_multipart_level ||
444 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
447 /* we are already reading parts of a multi-part level, but this level is
448 either not a multi-part level, or a part of a different multi-part
449 level; in both cases, the multi-part level seems to be complete */
454 /* ---------- here we have one part of a multi-part level ---------- */
456 reading_multipart_level = TRUE;
458 if (is_first_part) /* start with first part of new multi-part level */
460 /* copy level info structure from first part */
461 multipart_level = native_sp_level;
463 /* clear playfield of new multi-part level */
464 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
465 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
466 multipart_level.playfield[x][y] = fiSpace;
469 if (name_first == '?')
471 if (name_last == '?')
474 multipart_xpos = (int)(name_first - '0');
475 multipart_ypos = (int)(name_last - '0');
477 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
478 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
480 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
485 multipart_level.width = MAX(multipart_level.width,
486 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
487 multipart_level.height = MAX(multipart_level.height,
488 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
490 /* copy level part at the right position of multi-part level */
491 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
493 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
495 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
496 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
498 multipart_level.playfield[start_x + x][start_y + y] =
499 native_sp_level.playfield[x][y];
508 setLevelInfoToDefaults_SP();
510 Error(ERR_WARN, "single part of multi-part level -- using empty level");
513 if (reading_multipart_level)
514 native_sp_level = multipart_level;
516 copyInternalEngineVars_SP();
521 void SaveNativeLevel_SP(char *filename)
523 LevelInfoType *header = &native_sp_level.header;
527 if (!(file = fopen(filename, MODE_WRITE)))
529 Error(ERR_WARN, "cannot save native level file '%s'", filename);
534 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
535 for (y = 0; y < native_sp_level.height; y++)
536 for (x = 0; x < native_sp_level.width; x++)
537 putFile8Bit(file, native_sp_level.playfield[x][y]);
539 /* write level header (96 bytes) */
541 WriteUnusedBytesToFile(file, 4);
543 putFile8Bit(file, header->InitialGravity);
544 putFile8Bit(file, header->Version);
546 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
547 putFile8Bit(file, header->LevelTitle[i]);
549 putFile8Bit(file, header->InitialFreezeZonks);
550 putFile8Bit(file, header->InfotronsNeeded);
551 putFile8Bit(file, header->SpecialPortCount);
553 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
555 SpecialPortType *port = &header->SpecialPort[i];
557 putFile16BitBE(file, port->PortLocation);
558 putFile8Bit(file, port->Gravity);
559 putFile8Bit(file, port->FreezeZonks);
560 putFile8Bit(file, port->FreezeEnemies);
562 WriteUnusedBytesToFile(file, 1);
565 putFile8Bit(file, header->SpeedByte);
566 putFile8Bit(file, header->CheckSumByte);
567 putFile16BitLE(file, header->DemoRandomSeed);
569 /* also save demo tape, if available */
571 if (native_sp_level.demo.is_available)
573 putFile8Bit(file, native_sp_level.demo.level_nr);
575 for (i = 0; i < native_sp_level.demo.length; i++)
576 putFile8Bit(file, native_sp_level.demo.data[i]);
578 putFile8Bit(file, 0xff); /* "end of demo" byte */