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 /* (add one byte for the level number stored as first byte of demo data) */
76 FileMax = FieldMax + native_sp_level.demo.length + 1;
79 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
80 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
81 PlayField16 = REDIM_1D(sizeof(int), -game_sp.preceding_buffer_size, FieldMax);
85 for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
87 char *s = game_sp.preceding_buffer[i];
88 boolean hi_byte = FALSE; /* little endian data => start with low byte */
90 while (s[0] != '\0' && s[1] != '\0')
92 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
93 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
94 int byte = (hi_nibble << 4) | lo_nibble;
99 PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
114 for (y = 0; y < native_sp_level.height; y++)
115 for (x = 0; x < native_sp_level.width; x++)
116 PlayField8[count++] = native_sp_level.playfield[x][y];
118 /* add raw header bytes to subsequent playfield buffer zone */
119 for (i = 0; i < SP_HEADER_SIZE; i++)
120 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
122 for (i = 0; i < count; i++)
124 PlayField16[i] = PlayField8[i];
125 DisPlayField[i] = PlayField8[i];
129 if (native_sp_level.demo.is_available)
131 DemoAvailable = True;
134 /* !!! NEVER USED !!! */
135 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
137 /* !!! NEVER USED !!! */
138 for (i = 0; i < native_sp_level.demo.length; i++)
139 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
144 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
145 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
146 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
149 GravityFlag = LInfo.InitialGravity;
150 FreezeZonks = LInfo.InitialFreezeZonks;
153 /* this is set by main game tape code to native random generator directly */
155 RandomSeed = LInfo.DemoRandomSeed;
161 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
162 boolean demo_available)
164 LevelInfoType *header = &native_sp_level.header;
167 /* for details of the Supaplex level format, see Herman Perk's Supaplex
168 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
170 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
171 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
173 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
174 /* (MPX levels may have non-standard playfield size -- check max. size) */
175 for (y = 0; y < height; y++)
177 for (x = 0; x < width; x++)
179 byte element = getFile8Bit(file);
181 if (x < SP_MAX_PLAYFIELD_WIDTH &&
182 y < SP_MAX_PLAYFIELD_HEIGHT)
183 native_sp_level.playfield[x][y] = element;
187 /* read level header (96 bytes) */
189 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
191 /* initial gravity: 1 == "on", anything else (0) == "off" */
192 header->InitialGravity = getFile8Bit(file);
194 /* SpeedFixVersion XOR 0x20 */
195 header->Version = getFile8Bit(file);
197 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
198 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
199 header->LevelTitle[i] = getFile8Bit(file);
201 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
202 header->InitialFreezeZonks = getFile8Bit(file);
204 /* number of infotrons needed; 0 means that Supaplex will count the total
205 amount of infotrons in the level and use the low byte of that number
206 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
207 header->InfotronsNeeded = getFile8Bit(file);
209 /* number of special ("gravity") port entries below (maximum 10 allowed) */
210 header->SpecialPortCount = getFile8Bit(file);
212 /* database of properties of up to 10 special ports (6 bytes per port) */
213 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
215 SpecialPortType *port = &header->SpecialPort[i];
217 /* high and low byte of the location of a special port; if (x, y) are the
218 coordinates of a port in the field and (0, 0) is the top-left corner,
219 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
220 of what may be expected: Supaplex works with a game field in memory
221 which is 2 bytes per tile) */
222 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
224 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
225 port->Gravity = getFile8Bit(file);
227 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
228 port->FreezeZonks = getFile8Bit(file);
230 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
231 port->FreezeEnemies = getFile8Bit(file);
233 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
236 /* SpeedByte XOR Highbyte(RandomSeed) */
237 header->SpeedByte = getFile8Bit(file);
239 /* CheckSum XOR SpeedByte */
240 header->CheckSumByte = getFile8Bit(file);
242 /* random seed used for recorded demos */
243 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
245 /* auto-determine number of infotrons if it was stored as "0" -- see above */
246 if (header->InfotronsNeeded == 0)
248 for (x = 0; x < native_sp_level.width; x++)
249 for (y = 0; y < native_sp_level.height; y++)
250 if (native_sp_level.playfield[x][y] == fiInfotron)
251 header->InfotronsNeeded++;
253 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
256 /* read raw level header bytes (96 bytes) */
258 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
259 for (i = 0; i < SP_HEADER_SIZE; i++)
260 native_sp_level.header_raw_bytes[i] = fgetc(file);
262 /* also load demo tape, if available (only in single level files) */
266 int level_nr = getFile8Bit(file);
268 level_nr &= 0x7f; /* clear highest bit */
269 level_nr = (level_nr < 1 ? 1 :
270 level_nr > 111 ? 111 : level_nr);
272 native_sp_level.demo.level_nr = level_nr;
274 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
276 native_sp_level.demo.data[i] = getFile8Bit(file);
278 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
286 native_sp_level.demo.length = i;
287 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
291 boolean LoadNativeLevel_SP(char *filename, int level_pos)
295 char name_first, name_last;
296 struct LevelInfo_SP multipart_level;
297 int multipart_xpos, multipart_ypos;
298 boolean is_multipart_level;
299 boolean is_first_part;
300 boolean reading_multipart_level = FALSE;
301 boolean use_empty_level = FALSE;
302 LevelInfoType *header = &native_sp_level.header;
303 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
304 strSuffixLower(filename, ".mpx"));
305 boolean demo_available = is_single_level_file;
306 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
307 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
308 int level_width = SP_STD_PLAYFIELD_WIDTH;
309 int level_height = SP_STD_PLAYFIELD_HEIGHT;
311 /* always start with reliable default values */
312 setLevelInfoToDefaults_SP();
313 copyInternalEngineVars_SP();
315 if (!(file = fopen(filename, MODE_READ)))
317 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
324 char mpx_chunk_name[4 + 1];
327 LevelDescriptor *mpx_level_desc;
329 getFileChunkBE(file, mpx_chunk_name, NULL);
331 if (!strEqual(mpx_chunk_name, "MPX "))
333 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
339 mpx_version = getFile16BitLE(file);
341 if (mpx_version != 1)
343 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
349 mpx_level_count = getFile16BitLE(file);
351 if (mpx_level_count < 1)
353 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
359 if (level_pos >= mpx_level_count)
361 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
367 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
369 for (i = 0; i < mpx_level_count; i++)
371 LevelDescriptor *ldesc = &mpx_level_desc[i];
373 ldesc->Width = getFile16BitLE(file);
374 ldesc->Height = getFile16BitLE(file);
375 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
376 ldesc->Size = getFile32BitLE(file);
379 level_width = mpx_level_desc[level_pos].Width;
380 level_height = mpx_level_desc[level_pos].Height;
382 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
385 /* position file stream to the requested level (in case of level package) */
386 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
388 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
393 /* there exist Supaplex level package files with multi-part levels which
394 can be detected as follows: instead of leading and trailing dashes ('-')
395 to pad the level name, they have leading and trailing numbers which are
396 the x and y coordinations of the current part of the multi-part level;
397 if there are '?' characters instead of numbers on the left or right side
398 of the level name, the multi-part level consists of only horizontal or
401 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
403 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
406 /* check if this level is a part of a bigger multi-part level */
408 if (is_single_level_file)
411 name_first = header->LevelTitle[0];
412 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
415 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
416 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
419 ((name_first == '?' || name_first == '1') &&
420 (name_last == '?' || name_last == '1'));
422 if (is_multipart_level)
424 /* correct leading multipart level meta information in level name */
426 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
428 header->LevelTitle[i] = '-';
430 /* correct trailing multipart level meta information in level name */
431 for (i = SP_LEVEL_NAME_LEN - 1;
432 i >= 0 && header->LevelTitle[i] == name_last;
434 header->LevelTitle[i] = '-';
437 /* ---------- check for normal single level ---------- */
439 if (!reading_multipart_level && !is_multipart_level)
441 /* the current level is simply a normal single-part level, and we are
442 not reading a multi-part level yet, so return the level as it is */
447 /* ---------- check for empty level (unused multi-part) ---------- */
449 if (!reading_multipart_level && is_multipart_level && !is_first_part)
451 /* this is a part of a multi-part level, but not the first part
452 (and we are not already reading parts of a multi-part level);
453 in this case, use an empty level instead of the single part */
455 use_empty_level = TRUE;
460 /* ---------- check for finished multi-part level ---------- */
462 if (reading_multipart_level &&
463 (!is_multipart_level ||
464 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
467 /* we are already reading parts of a multi-part level, but this level is
468 either not a multi-part level, or a part of a different multi-part
469 level; in both cases, the multi-part level seems to be complete */
474 /* ---------- here we have one part of a multi-part level ---------- */
476 reading_multipart_level = TRUE;
478 if (is_first_part) /* start with first part of new multi-part level */
480 /* copy level info structure from first part */
481 multipart_level = native_sp_level;
483 /* clear playfield of new multi-part level */
484 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
485 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
486 multipart_level.playfield[x][y] = fiSpace;
489 if (name_first == '?')
491 if (name_last == '?')
494 multipart_xpos = (int)(name_first - '0');
495 multipart_ypos = (int)(name_last - '0');
498 printf("----------> part (%d/%d) of multi-part level '%s'\n",
499 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
502 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
503 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
505 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
510 multipart_level.width = MAX(multipart_level.width,
511 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
512 multipart_level.height = MAX(multipart_level.height,
513 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
515 /* copy level part at the right position of multi-part level */
516 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
518 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
520 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
521 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
523 multipart_level.playfield[start_x + x][start_y + y] =
524 native_sp_level.playfield[x][y];
533 setLevelInfoToDefaults_SP();
535 Error(ERR_WARN, "single part of multi-part level -- using empty level");
538 if (reading_multipart_level)
539 native_sp_level = multipart_level;
541 copyInternalEngineVars_SP();
546 void SaveNativeLevel_SP(char *filename)
548 LevelInfoType *header = &native_sp_level.header;
552 if (!(file = fopen(filename, MODE_WRITE)))
554 Error(ERR_WARN, "cannot save native level file '%s'", filename);
559 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
560 for (y = 0; y < native_sp_level.height; y++)
561 for (x = 0; x < native_sp_level.width; x++)
562 putFile8Bit(file, native_sp_level.playfield[x][y]);
564 /* write level header (96 bytes) */
566 WriteUnusedBytesToFile(file, 4);
568 putFile8Bit(file, header->InitialGravity);
569 putFile8Bit(file, header->Version);
571 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
572 putFile8Bit(file, header->LevelTitle[i]);
574 putFile8Bit(file, header->InitialFreezeZonks);
575 putFile8Bit(file, header->InfotronsNeeded);
576 putFile8Bit(file, header->SpecialPortCount);
578 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
580 SpecialPortType *port = &header->SpecialPort[i];
582 putFile16BitBE(file, port->PortLocation);
583 putFile8Bit(file, port->Gravity);
584 putFile8Bit(file, port->FreezeZonks);
585 putFile8Bit(file, port->FreezeEnemies);
587 WriteUnusedBytesToFile(file, 1);
590 putFile8Bit(file, header->SpeedByte);
591 putFile8Bit(file, header->CheckSumByte);
592 putFile16BitLE(file, header->DemoRandomSeed);
594 /* also save demo tape, if available */
596 if (native_sp_level.demo.is_available)
598 putFile8Bit(file, native_sp_level.demo.level_nr);
600 for (i = 0; i < native_sp_level.demo.length; i++)
601 putFile8Bit(file, native_sp_level.demo.data[i]);