6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level */
8 /* ------------------------------------------------------------------------- */
10 void setLevelInfoToDefaults_SP()
12 LevelInfoType *header = &native_sp_level.header;
13 char *empty_title = "-------- EMPTY --------";
16 native_sp_level.width = SP_PLAYFIELD_WIDTH;
17 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
19 for (x = 0; x < native_sp_level.width; x++)
20 for (y = 0; y < native_sp_level.height; y++)
21 native_sp_level.playfield[x][y] = fiSpace;
23 /* copy string (without terminating '\0' character!) */
24 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
25 header->LevelTitle[i] = empty_title[i];
27 header->InitialGravity = 0;
29 header->InitialFreezeZonks = 0;
30 header->InfotronsNeeded = 0;
31 header->SpecialPortCount = 0;
32 header->SpeedByte = 0;
33 header->CheckSumByte = 0;
34 header->DemoRandomSeed = 0;
36 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
38 SpecialPortType *port = &header->SpecialPort[i];
40 port->PortLocation = 0;
42 port->FreezeZonks = 0;
43 port->FreezeEnemies = 0;
46 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
47 for (i = 0; i < SP_HEADER_SIZE; i++)
48 native_sp_level.header_raw_bytes[i] = 0x20;
50 native_sp_level.demo.is_available = FALSE;
51 native_sp_level.demo.length = 0;
54 void copyInternalEngineVars_SP()
59 LInfo = native_sp_level.header;
61 FieldWidth = native_sp_level.width;
62 FieldHeight = native_sp_level.height;
65 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
66 LevelMax = (FieldWidth * FieldHeight) - 1;
68 FileMax = FieldMax + native_sp_level.demo.length;
70 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
71 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
73 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
75 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth * 2, FieldMax);
81 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
82 for (i = -FieldWidth * 2; i < -FieldWidth; i++)
83 PlayField16[i] = 0x20;
87 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
88 for (i = -FieldWidth; i < 0; i++)
89 PlayField16[i] = 0x20;
93 for (y = 0; y < native_sp_level.height; y++)
94 for (x = 0; x < native_sp_level.width; x++)
95 PlayField8[count++] = native_sp_level.playfield[x][y];
97 /* add raw header bytes to subsequent playfield buffer zone */
98 for (i = 0; i < SP_HEADER_SIZE; i++)
99 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
101 for (i = 0; i < count; i++)
103 PlayField16[i] = PlayField8[i];
104 DisPlayField[i] = PlayField8[i];
110 for (i = 0; y = 0; y < native_sp_level.height; y++)
112 for (x = 0; x < native_sp_level.width; x++)
114 PlayField8[i] = native_sp_level.playfield[x][y];
116 PlayField16[i] = PlayField8[i];
117 DisPlayField[i] = PlayField8[i];
126 if (native_sp_level.demo.is_available)
128 DemoAvailable = True;
130 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
132 for (i = 0; i < native_sp_level.demo.length; i++)
133 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
136 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
137 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
138 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
140 DemoPointer = FieldMax + 1;
141 DemoOffset = DemoPointer;
142 DemoKeyRepeatCounter = 0;
144 GravityFlag = LInfo.InitialGravity;
145 FreezeZonks = LInfo.InitialFreezeZonks;
148 /* this is set by main game tape code to native random generator directly */
152 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
155 RandomSeed = LInfo.DemoRandomSeed;
162 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
163 boolean demo_available)
165 LevelInfoType *header = &native_sp_level.header;
168 /* for details of the Supaplex level format, see Herman Perk's Supaplex
169 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
171 native_sp_level.width = width;
172 native_sp_level.height = height;
174 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
175 for (y = 0; y < native_sp_level.height; y++)
176 for (x = 0; x < native_sp_level.width; x++)
177 native_sp_level.playfield[x][y] = getFile8Bit(file);
179 /* read level header (96 bytes) */
181 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
183 /* initial gravity: 1 == "on", anything else (0) == "off" */
184 header->InitialGravity = getFile8Bit(file);
186 /* SpeedFixVersion XOR 0x20 */
187 header->Version = getFile8Bit(file);
189 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
190 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
191 header->LevelTitle[i] = getFile8Bit(file);
193 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
194 header->InitialFreezeZonks = getFile8Bit(file);
196 /* number of infotrons needed; 0 means that Supaplex will count the total
197 amount of infotrons in the level and use the low byte of that number
198 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
199 header->InfotronsNeeded = getFile8Bit(file);
201 /* number of special ("gravity") port entries below (maximum 10 allowed) */
202 header->SpecialPortCount = getFile8Bit(file);
205 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
208 /* database of properties of up to 10 special ports (6 bytes per port) */
209 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
211 SpecialPortType *port = &header->SpecialPort[i];
213 /* high and low byte of the location of a special port; if (x, y) are the
214 coordinates of a port in the field and (0, 0) is the top-left corner,
215 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
216 of what may be expected: Supaplex works with a game field in memory
217 which is 2 bytes per tile) */
218 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
222 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
223 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
225 printf("::: %d: port_location == %d => (%d, %d)\n",
226 i, port->PortLocation, port_x, port_y);
230 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
231 port->Gravity = getFile8Bit(file);
233 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
234 port->FreezeZonks = getFile8Bit(file);
236 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
237 port->FreezeEnemies = getFile8Bit(file);
239 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
242 /* SpeedByte XOR Highbyte(RandomSeed) */
243 header->SpeedByte = getFile8Bit(file);
245 /* CheckSum XOR SpeedByte */
246 header->CheckSumByte = getFile8Bit(file);
248 /* random seed used for recorded demos */
249 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
250 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
253 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
256 /* auto-determine number of infotrons if it was stored as "0" -- see above */
257 if (header->InfotronsNeeded == 0)
259 for (x = 0; x < native_sp_level.width; x++)
260 for (y = 0; y < native_sp_level.height; y++)
261 if (native_sp_level.playfield[x][y] == fiInfotron)
262 header->InfotronsNeeded++;
264 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
267 /* read raw level header bytes (96 bytes) */
269 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
270 for (i = 0; i < SP_HEADER_SIZE; i++)
271 native_sp_level.header_raw_bytes[i] = fgetc(file);
273 /* also load demo tape, if available (only in single level files) */
277 int level_nr = getFile8Bit(file);
279 level_nr &= 0x7f; /* clear highest bit */
280 level_nr = (level_nr < 1 ? 1 :
281 level_nr > 111 ? 111 : level_nr);
283 native_sp_level.demo.level_nr = level_nr;
285 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
287 native_sp_level.demo.data[i] = getFile8Bit(file);
289 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
297 native_sp_level.demo.length = i;
298 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
302 boolean LoadNativeLevel_SP(char *filename, int level_pos)
306 char name_first, name_last;
307 struct LevelInfo_SP multipart_level;
308 int multipart_xpos, multipart_ypos;
309 boolean is_multipart_level;
310 boolean is_first_part;
311 boolean reading_multipart_level = FALSE;
312 boolean use_empty_level = FALSE;
313 LevelInfoType *header = &native_sp_level.header;
314 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
315 strSuffixLower(filename, ".mpx"));
316 boolean demo_available = is_single_level_file;
317 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
318 int file_seek_pos = level_pos * SP_LEVEL_SIZE;
319 int level_width = SP_PLAYFIELD_WIDTH;
320 int level_height = SP_PLAYFIELD_HEIGHT;
322 /* always start with reliable default values */
323 setLevelInfoToDefaults_SP();
324 copyInternalEngineVars_SP();
326 if (!(file = fopen(filename, MODE_READ)))
328 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
335 char mpx_chunk_name[4 + 1];
338 LevelDescriptor *mpx_level_desc;
340 getFileChunkBE(file, mpx_chunk_name, NULL);
342 if (!strEqual(mpx_chunk_name, "MPX "))
344 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
350 mpx_version = getFile16BitLE(file);
352 if (mpx_version != 1)
354 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
360 mpx_level_count = getFile16BitLE(file);
362 if (mpx_level_count < 1)
364 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
370 if (level_pos >= mpx_level_count)
372 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
378 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
380 for (i = 0; i < mpx_level_count; i++)
382 LevelDescriptor *ldesc = &mpx_level_desc[i];
384 ldesc->Width = getFile16BitLE(file);
385 ldesc->Height = getFile16BitLE(file);
386 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
387 ldesc->Size = getFile32BitLE(file);
390 level_width = mpx_level_desc[level_pos].Width;
391 level_height = mpx_level_desc[level_pos].Height;
393 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
396 /* position file stream to the requested level (in case of level package) */
397 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
399 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
404 /* there exist Supaplex level package files with multi-part levels which
405 can be detected as follows: instead of leading and trailing dashes ('-')
406 to pad the level name, they have leading and trailing numbers which are
407 the x and y coordinations of the current part of the multi-part level;
408 if there are '?' characters instead of numbers on the left or right side
409 of the level name, the multi-part level consists of only horizontal or
412 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
414 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
417 /* check if this level is a part of a bigger multi-part level */
419 if (is_single_level_file)
422 name_first = header->LevelTitle[0];
423 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
426 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
427 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
430 ((name_first == '?' || name_first == '1') &&
431 (name_last == '?' || name_last == '1'));
433 if (is_multipart_level)
435 /* correct leading multipart level meta information in level name */
437 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
439 header->LevelTitle[i] = '-';
441 /* correct trailing multipart level meta information in level name */
442 for (i = SP_LEVEL_NAME_LEN - 1;
443 i >= 0 && header->LevelTitle[i] == name_last;
445 header->LevelTitle[i] = '-';
448 /* ---------- check for normal single level ---------- */
450 if (!reading_multipart_level && !is_multipart_level)
452 /* the current level is simply a normal single-part level, and we are
453 not reading a multi-part level yet, so return the level as it is */
458 /* ---------- check for empty level (unused multi-part) ---------- */
460 if (!reading_multipart_level && is_multipart_level && !is_first_part)
462 /* this is a part of a multi-part level, but not the first part
463 (and we are not already reading parts of a multi-part level);
464 in this case, use an empty level instead of the single part */
466 use_empty_level = TRUE;
471 /* ---------- check for finished multi-part level ---------- */
473 if (reading_multipart_level &&
474 (!is_multipart_level ||
475 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
478 /* we are already reading parts of a multi-part level, but this level is
479 either not a multi-part level, or a part of a different multi-part
480 level; in both cases, the multi-part level seems to be complete */
485 /* ---------- here we have one part of a multi-part level ---------- */
487 reading_multipart_level = TRUE;
489 if (is_first_part) /* start with first part of new multi-part level */
491 /* copy level info structure from first part */
492 multipart_level = native_sp_level;
494 /* clear playfield of new multi-part level */
495 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
496 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
497 multipart_level.playfield[x][y] = fiSpace;
500 if (name_first == '?')
502 if (name_last == '?')
505 multipart_xpos = (int)(name_first - '0');
506 multipart_ypos = (int)(name_last - '0');
509 printf("----------> part (%d/%d) of multi-part level '%s'\n",
510 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
513 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
514 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
516 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
521 multipart_level.width = MAX(multipart_level.width,
522 multipart_xpos * SP_PLAYFIELD_WIDTH);
523 multipart_level.height = MAX(multipart_level.height,
524 multipart_ypos * SP_PLAYFIELD_HEIGHT);
526 /* copy level part at the right position of multi-part level */
527 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
529 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
531 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
532 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
534 multipart_level.playfield[start_x + x][start_y + y] =
535 native_sp_level.playfield[x][y];
544 setLevelInfoToDefaults_SP();
546 Error(ERR_WARN, "single part of multi-part level -- using empty level");
549 if (reading_multipart_level)
550 native_sp_level = multipart_level;
552 copyInternalEngineVars_SP();