6 // ----------------------------------------------------------------------------
7 // functions for loading Supaplex level
8 // ----------------------------------------------------------------------------
10 static void setTapeInfoToDefaults_SP(void)
12 native_sp_level.demo.is_available = FALSE;
13 native_sp_level.demo.length = 0;
16 void setLevelInfoToDefaults_SP(void)
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(void)
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