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 native_sp_level.demo.length = i;
261 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
265 boolean LoadNativeLevel_SP(char *filename, int level_pos,
266 boolean level_info_only)
270 char name_first, name_last;
271 struct LevelInfo_SP multipart_level;
272 int multipart_xpos, multipart_ypos;
273 boolean is_multipart_level;
274 boolean is_first_part;
275 boolean reading_multipart_level = FALSE;
276 boolean use_empty_level = FALSE;
277 LevelInfoType *header = &native_sp_level.header;
278 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
279 strSuffixLower(filename, ".mpx"));
280 boolean demo_available = is_single_level_file;
281 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
282 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
283 int level_width = SP_STD_PLAYFIELD_WIDTH;
284 int level_height = SP_STD_PLAYFIELD_HEIGHT;
286 /* always start with reliable default values */
287 setLevelInfoToDefaults_SP();
288 copyInternalEngineVars_SP();
290 if (!(file = openFile(filename, MODE_READ)))
292 if (!level_info_only)
293 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
300 char mpx_chunk_name[4 + 1];
303 LevelDescriptor *mpx_level_desc;
305 getFileChunkBE(file, mpx_chunk_name, NULL);
307 if (!strEqual(mpx_chunk_name, "MPX "))
309 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
315 mpx_version = getFile16BitLE(file);
317 if (mpx_version != 1)
319 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
325 mpx_level_count = getFile16BitLE(file);
327 if (mpx_level_count < 1)
329 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
335 if (level_pos >= mpx_level_count)
337 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
343 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
345 for (i = 0; i < mpx_level_count; i++)
347 LevelDescriptor *ldesc = &mpx_level_desc[i];
349 ldesc->Width = getFile16BitLE(file);
350 ldesc->Height = getFile16BitLE(file);
351 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
352 ldesc->Size = getFile32BitLE(file);
355 level_width = mpx_level_desc[level_pos].Width;
356 level_height = mpx_level_desc[level_pos].Height;
358 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
361 /* position file stream to the requested level (in case of level package) */
362 if (seekFile(file, file_seek_pos, SEEK_SET) != 0)
364 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
369 /* there exist Supaplex level package files with multi-part levels which
370 can be detected as follows: instead of leading and trailing dashes ('-')
371 to pad the level name, they have leading and trailing numbers which are
372 the x and y coordinations of the current part of the multi-part level;
373 if there are '?' characters instead of numbers on the left or right side
374 of the level name, the multi-part level consists of only horizontal or
377 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
379 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
382 /* check if this level is a part of a bigger multi-part level */
384 if (is_single_level_file)
387 name_first = header->LevelTitle[0];
388 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
391 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
392 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
395 ((name_first == '?' || name_first == '1') &&
396 (name_last == '?' || name_last == '1'));
398 if (is_multipart_level)
400 /* correct leading multipart level meta information in level name */
402 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
404 header->LevelTitle[i] = '-';
406 /* correct trailing multipart level meta information in level name */
407 for (i = SP_LEVEL_NAME_LEN - 1;
408 i >= 0 && header->LevelTitle[i] == name_last;
410 header->LevelTitle[i] = '-';
413 /* ---------- check for normal single level ---------- */
415 if (!reading_multipart_level && !is_multipart_level)
417 /* the current level is simply a normal single-part level, and we are
418 not reading a multi-part level yet, so return the level as it is */
423 /* ---------- check for empty level (unused multi-part) ---------- */
425 if (!reading_multipart_level && is_multipart_level && !is_first_part)
427 /* this is a part of a multi-part level, but not the first part
428 (and we are not already reading parts of a multi-part level);
429 in this case, use an empty level instead of the single part */
431 use_empty_level = TRUE;
436 /* ---------- check for finished multi-part level ---------- */
438 if (reading_multipart_level &&
439 (!is_multipart_level ||
440 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
443 /* we are already reading parts of a multi-part level, but this level is
444 either not a multi-part level, or a part of a different multi-part
445 level; in both cases, the multi-part level seems to be complete */
450 /* ---------- here we have one part of a multi-part level ---------- */
452 reading_multipart_level = TRUE;
454 if (is_first_part) /* start with first part of new multi-part level */
456 /* copy level info structure from first part */
457 multipart_level = native_sp_level;
459 /* clear playfield of new multi-part level */
460 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
461 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
462 multipart_level.playfield[x][y] = fiSpace;
465 if (name_first == '?')
467 if (name_last == '?')
470 multipart_xpos = (int)(name_first - '0');
471 multipart_ypos = (int)(name_last - '0');
473 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
474 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
476 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
481 multipart_level.width = MAX(multipart_level.width,
482 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
483 multipart_level.height = MAX(multipart_level.height,
484 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
486 /* copy level part at the right position of multi-part level */
487 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
489 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
491 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
492 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
494 multipart_level.playfield[start_x + x][start_y + y] =
495 native_sp_level.playfield[x][y];
504 setLevelInfoToDefaults_SP();
506 Error(ERR_WARN, "single part of multi-part level -- using empty level");
509 if (reading_multipart_level)
510 native_sp_level = multipart_level;
512 copyInternalEngineVars_SP();
517 void SaveNativeLevel_SP(char *filename)
519 LevelInfoType *header = &native_sp_level.header;
523 if (!(file = fopen(filename, MODE_WRITE)))
525 Error(ERR_WARN, "cannot save native level file '%s'", filename);
530 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
531 for (y = 0; y < native_sp_level.height; y++)
532 for (x = 0; x < native_sp_level.width; x++)
533 putFile8Bit(file, native_sp_level.playfield[x][y]);
535 /* write level header (96 bytes) */
537 WriteUnusedBytesToFile(file, 4);
539 putFile8Bit(file, header->InitialGravity);
540 putFile8Bit(file, header->Version);
542 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
543 putFile8Bit(file, header->LevelTitle[i]);
545 putFile8Bit(file, header->InitialFreezeZonks);
546 putFile8Bit(file, header->InfotronsNeeded);
547 putFile8Bit(file, header->SpecialPortCount);
549 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
551 SpecialPortType *port = &header->SpecialPort[i];
553 putFile16BitBE(file, port->PortLocation);
554 putFile8Bit(file, port->Gravity);
555 putFile8Bit(file, port->FreezeZonks);
556 putFile8Bit(file, port->FreezeEnemies);
558 WriteUnusedBytesToFile(file, 1);
561 putFile8Bit(file, header->SpeedByte);
562 putFile8Bit(file, header->CheckSumByte);
563 putFile16BitLE(file, header->DemoRandomSeed);
565 /* also save demo tape, if available */
567 if (native_sp_level.demo.is_available)
569 putFile8Bit(file, native_sp_level.demo.level_nr);
571 for (i = 0; i < native_sp_level.demo.length; i++)
572 putFile8Bit(file, native_sp_level.demo.data[i]);
574 putFile8Bit(file, 0xff); /* "end of demo" byte */