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.width = SP_STD_PLAYFIELD_WIDTH;
23 native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
25 for (x = 0; x < native_sp_level.width; x++)
26 for (y = 0; y < native_sp_level.height; y++)
27 native_sp_level.playfield[x][y] = fiSpace;
29 // copy string (without terminating '\0' character!)
30 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
31 header->LevelTitle[i] = empty_title[i];
33 header->InitialGravity = 0;
35 header->InitialFreezeZonks = 0;
36 header->InfotronsNeeded = 0;
37 header->SpecialPortCount = 0;
38 header->SpeedByte = 0;
39 header->CheckSumByte = 0;
40 header->DemoRandomSeed = 0;
42 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
44 SpecialPortType *port = &header->SpecialPort[i];
46 port->PortLocation = 0;
48 port->FreezeZonks = 0;
49 port->FreezeEnemies = 0;
52 // set raw header bytes (used for subsequent buffer zone) to "hardware"
53 for (i = 0; i < SP_HEADER_SIZE; i++)
54 native_sp_level.header_raw_bytes[i] = 0x20;
56 setTapeInfoToDefaults_SP();
59 void copyInternalEngineVars_SP(void)
64 LInfo = native_sp_level.header;
66 FieldWidth = native_sp_level.width;
67 FieldHeight = native_sp_level.height;
70 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
71 LevelMax = (FieldWidth * FieldHeight) - 1;
73 // initialize preceding playfield buffer
74 for (i = -game_sp.preceding_buffer_size; i < 0; i++)
77 // initialize preceding playfield buffer
78 for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++)
82 for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
84 char *s = game_sp.preceding_buffer[i];
85 boolean hi_byte = FALSE; // little endian data => start with low byte
87 while (s[0] != '\0' && s[1] != '\0')
89 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
90 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
91 int byte = (hi_nibble << 4) | lo_nibble;
96 PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
111 for (y = 0; y < native_sp_level.height; y++)
112 for (x = 0; x < native_sp_level.width; x++)
113 PlayField8[count++] = native_sp_level.playfield[x][y];
115 // add raw header bytes to subsequent playfield buffer zone
116 for (i = 0; i < SP_HEADER_SIZE; i++)
117 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
119 for (i = 0; i < count; i++)
121 PlayField16[i] = PlayField8[i];
122 DisPlayField[i] = PlayField8[i];
126 if (native_sp_level.demo.is_available)
127 DemoAvailable = True;
129 GravityFlag = LInfo.InitialGravity;
130 FreezeZonks = LInfo.InitialFreezeZonks;
134 // random seed set by main game tape code to native random generator seed
137 static void LoadNativeLevelFromFileStream_SP(File *file, int width, int height,
138 boolean demo_available)
140 LevelInfoType *header = &native_sp_level.header;
143 // for details of the Supaplex level format, see Herman Perk's Supaplex
144 // documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package
146 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
147 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
149 // read level playfield (width * height == 60 * 24 tiles == 1440 bytes)
150 // (MPX levels may have non-standard playfield size -- check max. size)
151 for (y = 0; y < height; y++)
153 for (x = 0; x < width; x++)
155 byte element = getFile8Bit(file);
157 if (x < SP_MAX_PLAYFIELD_WIDTH &&
158 y < SP_MAX_PLAYFIELD_HEIGHT)
159 native_sp_level.playfield[x][y] = element;
163 // read level header (96 bytes)
165 ReadUnusedBytesFromFile(file, 4); // (not used by Supaplex engine)
167 // initial gravity: 1 == "on", anything else (0) == "off"
168 header->InitialGravity = getFile8Bit(file);
170 // SpeedFixVersion XOR 0x20
171 header->Version = getFile8Bit(file);
173 // level title in uppercase letters, padded with dashes ("-") (23 bytes)
174 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
175 header->LevelTitle[i] = getFile8Bit(file);
177 // initial "freeze zonks": 2 == "on", anything else (0, 1) == "off"
178 header->InitialFreezeZonks = getFile8Bit(file);
180 // number of infotrons needed; 0 means that Supaplex will count the total
181 // amount of infotrons in the level and use the low byte of that number
182 // (a multiple of 256 infotrons will result in "0 infotrons needed"!)
183 header->InfotronsNeeded = getFile8Bit(file);
185 // number of special ("gravity") port entries below (maximum 10 allowed)
186 header->SpecialPortCount = getFile8Bit(file);
188 // database of properties of up to 10 special ports (6 bytes per port)
189 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
191 SpecialPortType *port = &header->SpecialPort[i];
193 /* high and low byte of the location of a special port; if (x, y) are the
194 coordinates of a port in the field and (0, 0) is the top-left corner,
195 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
196 of what may be expected: Supaplex works with a game field in memory
197 which is 2 bytes per tile) */
198 port->PortLocation = getFile16BitBE(file); // yes, big endian
200 // change gravity: 1 == "turn on", anything else (0) == "turn off"
201 port->Gravity = getFile8Bit(file);
203 // "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off"
204 port->FreezeZonks = getFile8Bit(file);
206 // "freeze enemies": 1 == "turn on", anything else (0) == "turn off"
207 port->FreezeEnemies = getFile8Bit(file);
209 ReadUnusedBytesFromFile(file, 1); // (not used by Supaplex engine)
212 // SpeedByte XOR Highbyte(RandomSeed)
213 header->SpeedByte = getFile8Bit(file);
215 // CheckSum XOR SpeedByte
216 header->CheckSumByte = getFile8Bit(file);
218 // random seed used for recorded demos
219 header->DemoRandomSeed = getFile16BitLE(file); // yes, little endian
221 // auto-determine number of infotrons if it was stored as "0" -- see above
222 if (header->InfotronsNeeded == 0)
224 for (x = 0; x < native_sp_level.width; x++)
225 for (y = 0; y < native_sp_level.height; y++)
226 if (native_sp_level.playfield[x][y] == fiInfotron)
227 header->InfotronsNeeded++;
229 header->InfotronsNeeded &= 0xff; // only use low byte -- see above
232 // read raw level header bytes (96 bytes)
234 seekFile(file, -(SP_HEADER_SIZE), SEEK_CUR); // rewind file
235 for (i = 0; i < SP_HEADER_SIZE; i++)
236 native_sp_level.header_raw_bytes[i] = getByteFromFile(file);
238 // also load demo tape, if available (only in single level files)
242 int level_nr = getFile8Bit(file);
244 level_nr &= 0x7f; // clear highest bit
245 level_nr = (level_nr < 1 ? 1 :
246 level_nr > 111 ? 111 : level_nr);
248 native_sp_level.demo.level_nr = level_nr;
250 for (i = 0; i < SP_MAX_TAPE_LEN && !checkEndOfFile(file); i++)
252 native_sp_level.demo.data[i] = getFile8Bit(file);
254 if (native_sp_level.demo.data[i] == 0xff) // "end of demo" byte
258 if (i >= SP_MAX_TAPE_LEN)
259 Warn("SP demo truncated: size exceeds maximum SP demo size %d",
262 native_sp_level.demo.length = i;
263 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
267 boolean LoadNativeLevel_SP(char *filename, int level_pos,
268 boolean level_info_only)
272 char name_first, name_last;
273 struct LevelInfo_SP multipart_level;
274 int multipart_xpos, multipart_ypos;
275 boolean is_multipart_level;
276 boolean is_first_part;
277 boolean reading_multipart_level = FALSE;
278 boolean use_empty_level = FALSE;
279 LevelInfoType *header = &native_sp_level.header;
280 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
281 strSuffixLower(filename, ".mpx"));
282 boolean demo_available = is_single_level_file;
283 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
284 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
285 int level_width = SP_STD_PLAYFIELD_WIDTH;
286 int level_height = SP_STD_PLAYFIELD_HEIGHT;
288 // always start with reliable default values
289 setLevelInfoToDefaults_SP();
290 copyInternalEngineVars_SP();
292 if (!(file = openFile(filename, MODE_READ)))
294 if (!level_info_only)
295 Warn("cannot open file '%s' -- using empty level", filename);
302 char mpx_chunk_name[4 + 1];
305 LevelDescriptor *mpx_level_desc;
307 getFileChunkBE(file, mpx_chunk_name, NULL);
309 if (!strEqual(mpx_chunk_name, "MPX "))
311 Warn("cannot find MPX ID in file '%s' -- using empty level", filename);
316 mpx_version = getFile16BitLE(file);
318 if (mpx_version != 1)
320 Warn("unknown MPX version in file '%s' -- using empty level", filename);
325 mpx_level_count = getFile16BitLE(file);
327 if (mpx_level_count < 1)
329 Warn("no MPX levels found in file '%s' -- using empty level", filename);
334 if (level_pos >= mpx_level_count)
336 Warn("MPX level not found in file '%s' -- using empty level", filename);
341 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
343 for (i = 0; i < mpx_level_count; i++)
345 LevelDescriptor *ldesc = &mpx_level_desc[i];
347 ldesc->Width = getFile16BitLE(file);
348 ldesc->Height = getFile16BitLE(file);
349 ldesc->OffSet = getFile32BitLE(file); // starts with 1, not with 0
350 ldesc->Size = getFile32BitLE(file);
353 level_width = mpx_level_desc[level_pos].Width;
354 level_height = mpx_level_desc[level_pos].Height;
356 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
359 // position file stream to the requested level (in case of level package)
360 if (seekFile(file, file_seek_pos, SEEK_SET) != 0)
362 Warn("cannot fseek in file '%s' -- using empty level", filename);
367 /* there exist Supaplex level package files with multi-part levels which
368 can be detected as follows: instead of leading and trailing dashes ('-')
369 to pad the level name, they have leading and trailing numbers which are
370 the x and y coordinations of the current part of the multi-part level;
371 if there are '?' characters instead of numbers on the left or right side
372 of the level name, the multi-part level consists of only horizontal or
375 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
377 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
380 // check if this level is a part of a bigger multi-part level
382 if (is_single_level_file)
385 name_first = header->LevelTitle[0];
386 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
389 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
390 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
393 ((name_first == '?' || name_first == '1') &&
394 (name_last == '?' || name_last == '1'));
396 if (is_multipart_level)
398 // correct leading multipart level meta information in level name
400 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
402 header->LevelTitle[i] = '-';
404 // correct trailing multipart level meta information in level name
405 for (i = SP_LEVEL_NAME_LEN - 1;
406 i >= 0 && header->LevelTitle[i] == name_last;
408 header->LevelTitle[i] = '-';
411 // ---------- check for normal single level ----------
413 if (!reading_multipart_level && !is_multipart_level)
415 // the current level is simply a normal single-part level, and we are
416 // not reading a multi-part level yet, so return the level as it is
421 // ---------- check for empty level (unused multi-part) ----------
423 if (!reading_multipart_level && is_multipart_level && !is_first_part)
425 // this is a part of a multi-part level, but not the first part
426 // (and we are not already reading parts of a multi-part level);
427 // in this case, use an empty level instead of the single part
429 use_empty_level = TRUE;
434 // ---------- check for finished multi-part level ----------
436 if (reading_multipart_level &&
437 (!is_multipart_level ||
438 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
441 // we are already reading parts of a multi-part level, but this level is
442 // either not a multi-part level, or a part of a different multi-part
443 // level; in both cases, the multi-part level seems to be complete
448 // ---------- here we have one part of a multi-part level ----------
450 reading_multipart_level = TRUE;
452 if (is_first_part) // start with first part of new multi-part level
454 // copy level info structure from first part
455 multipart_level = native_sp_level;
457 // clear playfield of new multi-part level
458 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
459 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
460 multipart_level.playfield[x][y] = fiSpace;
463 if (name_first == '?')
465 if (name_last == '?')
468 multipart_xpos = (int)(name_first - '0');
469 multipart_ypos = (int)(name_last - '0');
471 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
472 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
474 Warn("multi-part level is too big -- ignoring part of it");
479 multipart_level.width = MAX(multipart_level.width,
480 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
481 multipart_level.height = MAX(multipart_level.height,
482 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
484 // copy level part at the right position of multi-part level
485 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
487 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
489 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
490 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
492 multipart_level.playfield[start_x + x][start_y + y] =
493 native_sp_level.playfield[x][y];
502 setLevelInfoToDefaults_SP();
504 Warn("single part of multi-part level -- using empty level");
507 if (reading_multipart_level)
508 native_sp_level = multipart_level;
510 copyInternalEngineVars_SP();
515 void SaveNativeLevel_SP(char *filename)
517 LevelInfoType *header = &native_sp_level.header;
521 if (!(file = fopen(filename, MODE_WRITE)))
523 Warn("cannot save native level file '%s'", filename);
528 // write level playfield (width * height == 60 * 24 tiles == 1440 bytes)
529 for (y = 0; y < native_sp_level.height; y++)
530 for (x = 0; x < native_sp_level.width; x++)
531 putFile8Bit(file, native_sp_level.playfield[x][y]);
533 // write level header (96 bytes)
535 WriteUnusedBytesToFile(file, 4);
537 putFile8Bit(file, header->InitialGravity);
538 putFile8Bit(file, header->Version);
540 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
541 putFile8Bit(file, header->LevelTitle[i]);
543 putFile8Bit(file, header->InitialFreezeZonks);
544 putFile8Bit(file, header->InfotronsNeeded);
545 putFile8Bit(file, header->SpecialPortCount);
547 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
549 SpecialPortType *port = &header->SpecialPort[i];
551 putFile16BitBE(file, port->PortLocation);
552 putFile8Bit(file, port->Gravity);
553 putFile8Bit(file, port->FreezeZonks);
554 putFile8Bit(file, port->FreezeEnemies);
556 WriteUnusedBytesToFile(file, 1);
559 putFile8Bit(file, header->SpeedByte);
560 putFile8Bit(file, header->CheckSumByte);
561 putFile16BitLE(file, header->DemoRandomSeed);
563 // also save demo tape, if available
565 if (native_sp_level.demo.is_available)
567 putFile8Bit(file, native_sp_level.demo.level_nr);
569 for (i = 0; i < native_sp_level.demo.length; i++)
570 putFile8Bit(file, native_sp_level.demo.data[i]);
572 putFile8Bit(file, 0xff); // "end of demo" byte