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);
72 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
77 /* fill preceding playfield buffer zone with (indestructible) "hardware" */
78 for (i = -FieldWidth; i < 0; i++)
79 PlayField16[i] = 0x20;
83 for (y = 0; y < native_sp_level.height; y++)
84 for (x = 0; x < native_sp_level.width; x++)
85 PlayField8[count++] = native_sp_level.playfield[x][y];
87 /* add raw header bytes to subsequent playfield buffer zone */
88 for (i = 0; i < SP_HEADER_SIZE; i++)
89 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
91 for (i = 0; i < count; i++)
93 PlayField16[i] = PlayField8[i];
94 DisPlayField[i] = PlayField8[i];
100 for (i = 0; y = 0; y < native_sp_level.height; y++)
102 for (x = 0; x < native_sp_level.width; x++)
104 PlayField8[i] = native_sp_level.playfield[x][y];
106 PlayField16[i] = PlayField8[i];
107 DisPlayField[i] = PlayField8[i];
116 if (native_sp_level.demo.is_available)
118 DemoAvailable = True;
120 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
122 for (i = 0; i < native_sp_level.demo.length; i++)
123 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
126 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
127 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
128 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
130 DemoPointer = FieldMax + 1;
131 DemoOffset = DemoPointer;
132 DemoKeyRepeatCounter = 0;
134 GravityFlag = LInfo.InitialGravity;
135 FreezeZonks = LInfo.InitialFreezeZonks;
138 /* this is set by main game tape code to native random generator directly */
142 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
145 RandomSeed = LInfo.DemoRandomSeed;
152 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
153 boolean demo_available)
155 LevelInfoType *header = &native_sp_level.header;
158 /* for details of the Supaplex level format, see Herman Perk's Supaplex
159 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
161 native_sp_level.width = width;
162 native_sp_level.height = height;
164 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
165 for (y = 0; y < native_sp_level.height; y++)
166 for (x = 0; x < native_sp_level.width; x++)
167 native_sp_level.playfield[x][y] = getFile8Bit(file);
169 /* read level header (96 bytes) */
171 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
173 /* initial gravity: 1 == "on", anything else (0) == "off" */
174 header->InitialGravity = getFile8Bit(file);
176 /* SpeedFixVersion XOR 0x20 */
177 header->Version = getFile8Bit(file);
179 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
180 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
181 header->LevelTitle[i] = getFile8Bit(file);
183 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
184 header->InitialFreezeZonks = getFile8Bit(file);
186 /* number of infotrons needed; 0 means that Supaplex will count the total
187 amount of infotrons in the level and use the low byte of that number
188 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
189 header->InfotronsNeeded = getFile8Bit(file);
191 /* number of special ("gravity") port entries below (maximum 10 allowed) */
192 header->SpecialPortCount = getFile8Bit(file);
195 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
198 /* database of properties of up to 10 special ports (6 bytes per port) */
199 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
201 SpecialPortType *port = &header->SpecialPort[i];
203 /* high and low byte of the location of a special port; if (x, y) are the
204 coordinates of a port in the field and (0, 0) is the top-left corner,
205 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
206 of what may be expected: Supaplex works with a game field in memory
207 which is 2 bytes per tile) */
208 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
212 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
213 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
215 printf("::: %d: port_location == %d => (%d, %d)\n",
216 i, port->PortLocation, port_x, port_y);
220 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
221 port->Gravity = getFile8Bit(file);
223 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
224 port->FreezeZonks = getFile8Bit(file);
226 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
227 port->FreezeEnemies = getFile8Bit(file);
229 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
232 /* SpeedByte XOR Highbyte(RandomSeed) */
233 header->SpeedByte = getFile8Bit(file);
235 /* CheckSum XOR SpeedByte */
236 header->CheckSumByte = getFile8Bit(file);
238 /* random seed used for recorded demos */
239 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
240 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
243 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
246 /* auto-determine number of infotrons if it was stored as "0" -- see above */
247 if (header->InfotronsNeeded == 0)
249 for (x = 0; x < native_sp_level.width; x++)
250 for (y = 0; y < native_sp_level.height; y++)
251 if (native_sp_level.playfield[x][y] == fiInfotron)
252 header->InfotronsNeeded++;
254 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
257 /* read raw level header bytes (96 bytes) */
259 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
260 for (i = 0; i < SP_HEADER_SIZE; i++)
261 native_sp_level.header_raw_bytes[i] = fgetc(file);
263 /* also load demo tape, if available (only in single level files) */
267 int level_nr = getFile8Bit(file);
269 level_nr &= 0x7f; /* clear highest bit */
270 level_nr = (level_nr < 1 ? 1 :
271 level_nr > 111 ? 111 : level_nr);
273 native_sp_level.demo.level_nr = level_nr;
275 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
277 native_sp_level.demo.data[i] = getFile8Bit(file);
279 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
287 native_sp_level.demo.length = i;
288 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
292 boolean LoadNativeLevel_SP(char *filename, int level_pos)
296 char name_first, name_last;
297 struct LevelInfo_SP multipart_level;
298 int multipart_xpos, multipart_ypos;
299 boolean is_multipart_level;
300 boolean is_first_part;
301 boolean reading_multipart_level = FALSE;
302 boolean use_empty_level = FALSE;
303 LevelInfoType *header = &native_sp_level.header;
304 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
305 strSuffixLower(filename, ".mpx"));
306 boolean demo_available = is_single_level_file;
307 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
308 int file_seek_pos = level_pos * SP_LEVEL_SIZE;
309 int level_width = SP_PLAYFIELD_WIDTH;
310 int level_height = SP_PLAYFIELD_HEIGHT;
312 /* always start with reliable default values */
313 setLevelInfoToDefaults_SP();
314 copyInternalEngineVars_SP();
316 if (!(file = fopen(filename, MODE_READ)))
318 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
325 char mpx_chunk_name[4 + 1];
328 LevelDescriptor *mpx_level_desc;
330 getFileChunkBE(file, mpx_chunk_name, NULL);
332 if (!strEqual(mpx_chunk_name, "MPX "))
334 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
340 mpx_version = getFile16BitLE(file);
342 if (mpx_version != 1)
344 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
350 mpx_level_count = getFile16BitLE(file);
352 if (mpx_level_count < 1)
354 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
360 if (level_pos >= mpx_level_count)
362 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
368 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
370 for (i = 0; i < mpx_level_count; i++)
372 LevelDescriptor *ldesc = &mpx_level_desc[i];
374 ldesc->Width = getFile16BitLE(file);
375 ldesc->Height = getFile16BitLE(file);
376 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
377 ldesc->Size = getFile32BitLE(file);
380 level_width = mpx_level_desc[level_pos].Width;
381 level_height = mpx_level_desc[level_pos].Height;
383 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
386 /* position file stream to the requested level (in case of level package) */
387 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
389 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
394 /* there exist Supaplex level package files with multi-part levels which
395 can be detected as follows: instead of leading and trailing dashes ('-')
396 to pad the level name, they have leading and trailing numbers which are
397 the x and y coordinations of the current part of the multi-part level;
398 if there are '?' characters instead of numbers on the left or right side
399 of the level name, the multi-part level consists of only horizontal or
402 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
404 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
407 /* check if this level is a part of a bigger multi-part level */
409 if (is_single_level_file)
412 name_first = header->LevelTitle[0];
413 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
416 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
417 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
420 ((name_first == '?' || name_first == '1') &&
421 (name_last == '?' || name_last == '1'));
423 if (is_multipart_level)
425 /* correct leading multipart level meta information in level name */
427 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
429 header->LevelTitle[i] = '-';
431 /* correct trailing multipart level meta information in level name */
432 for (i = SP_LEVEL_NAME_LEN - 1;
433 i >= 0 && header->LevelTitle[i] == name_last;
435 header->LevelTitle[i] = '-';
438 /* ---------- check for normal single level ---------- */
440 if (!reading_multipart_level && !is_multipart_level)
442 /* the current level is simply a normal single-part level, and we are
443 not reading a multi-part level yet, so return the level as it is */
448 /* ---------- check for empty level (unused multi-part) ---------- */
450 if (!reading_multipart_level && is_multipart_level && !is_first_part)
452 /* this is a part of a multi-part level, but not the first part
453 (and we are not already reading parts of a multi-part level);
454 in this case, use an empty level instead of the single part */
456 use_empty_level = TRUE;
461 /* ---------- check for finished multi-part level ---------- */
463 if (reading_multipart_level &&
464 (!is_multipart_level ||
465 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
468 /* we are already reading parts of a multi-part level, but this level is
469 either not a multi-part level, or a part of a different multi-part
470 level; in both cases, the multi-part level seems to be complete */
475 /* ---------- here we have one part of a multi-part level ---------- */
477 reading_multipart_level = TRUE;
479 if (is_first_part) /* start with first part of new multi-part level */
481 /* copy level info structure from first part */
482 multipart_level = native_sp_level;
484 /* clear playfield of new multi-part level */
485 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
486 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
487 multipart_level.playfield[x][y] = fiSpace;
490 if (name_first == '?')
492 if (name_last == '?')
495 multipart_xpos = (int)(name_first - '0');
496 multipart_ypos = (int)(name_last - '0');
499 printf("----------> part (%d/%d) of multi-part level '%s'\n",
500 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
503 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
504 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
506 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
511 multipart_level.width = MAX(multipart_level.width,
512 multipart_xpos * SP_PLAYFIELD_WIDTH);
513 multipart_level.height = MAX(multipart_level.height,
514 multipart_ypos * SP_PLAYFIELD_HEIGHT);
516 /* copy level part at the right position of multi-part level */
517 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
519 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
521 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
522 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
524 multipart_level.playfield[start_x + x][start_y + y] =
525 native_sp_level.playfield[x][y];
534 setLevelInfoToDefaults_SP();
536 Error(ERR_WARN, "single part of multi-part level -- using empty level");
539 if (reading_multipart_level)
540 native_sp_level = multipart_level;
542 copyInternalEngineVars_SP();