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, boolean demo_available)
154 LevelInfoType *header = &native_sp_level.header;
157 /* for details of the Supaplex level format, see Herman Perk's Supaplex
158 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
160 native_sp_level.width = SP_PLAYFIELD_WIDTH;
161 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
163 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
164 for (y = 0; y < native_sp_level.height; y++)
165 for (x = 0; x < native_sp_level.width; x++)
166 native_sp_level.playfield[x][y] = getFile8Bit(file);
168 /* read level header (96 bytes) */
170 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
172 /* initial gravity: 1 == "on", anything else (0) == "off" */
173 header->InitialGravity = getFile8Bit(file);
175 /* SpeedFixVersion XOR 0x20 */
176 header->Version = getFile8Bit(file);
178 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
179 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
180 header->LevelTitle[i] = getFile8Bit(file);
182 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
183 header->InitialFreezeZonks = getFile8Bit(file);
185 /* number of infotrons needed; 0 means that Supaplex will count the total
186 amount of infotrons in the level and use the low byte of that number
187 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
188 header->InfotronsNeeded = getFile8Bit(file);
190 /* number of special ("gravity") port entries below (maximum 10 allowed) */
191 header->SpecialPortCount = getFile8Bit(file);
194 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
197 /* database of properties of up to 10 special ports (6 bytes per port) */
198 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
200 SpecialPortType *port = &header->SpecialPort[i];
202 /* high and low byte of the location of a special port; if (x, y) are the
203 coordinates of a port in the field and (0, 0) is the top-left corner,
204 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
205 of what may be expected: Supaplex works with a game field in memory
206 which is 2 bytes per tile) */
207 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
211 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
212 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
214 printf("::: %d: port_location == %d => (%d, %d)\n",
215 i, port->PortLocation, port_x, port_y);
219 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
220 port->Gravity = getFile8Bit(file);
222 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
223 port->FreezeZonks = getFile8Bit(file);
225 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
226 port->FreezeEnemies = getFile8Bit(file);
228 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
231 /* SpeedByte XOR Highbyte(RandomSeed) */
232 header->SpeedByte = getFile8Bit(file);
234 /* CheckSum XOR SpeedByte */
235 header->CheckSumByte = getFile8Bit(file);
237 /* random seed used for recorded demos */
238 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
239 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
242 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
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 */
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 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 demo_available = (strSuffix(filename, ".sp") ||
304 strSuffix(filename, ".SP"));
306 /* always start with reliable default values */
307 setLevelInfoToDefaults_SP();
308 copyInternalEngineVars_SP();
310 if (!(file = fopen(filename, MODE_READ)))
312 Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
317 /* position file stream to the requested level (in case of level package) */
318 if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
320 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
325 /* there exist Supaplex level package files with multi-part levels which
326 can be detected as follows: instead of leading and trailing dashes ('-')
327 to pad the level name, they have leading and trailing numbers which are
328 the x and y coordinations of the current part of the multi-part level;
329 if there are '?' characters instead of numbers on the left or right side
330 of the level name, the multi-part level consists of only horizontal or
333 for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
335 LoadNativeLevelFromFileStream_SP(file, demo_available);
337 /* check if this level is a part of a bigger multi-part level */
339 name_first = header->LevelTitle[0];
340 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
343 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
344 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
347 ((name_first == '?' || name_first == '1') &&
348 (name_last == '?' || name_last == '1'));
350 if (is_multipart_level)
352 /* correct leading multipart level meta information in level name */
354 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
356 header->LevelTitle[i] = '-';
358 /* correct trailing multipart level meta information in level name */
359 for (i = SP_LEVEL_NAME_LEN - 1;
360 i >= 0 && header->LevelTitle[i] == name_last;
362 header->LevelTitle[i] = '-';
365 /* ---------- check for normal single level ---------- */
367 if (!reading_multipart_level && !is_multipart_level)
369 /* the current level is simply a normal single-part level, and we are
370 not reading a multi-part level yet, so return the level as it is */
375 /* ---------- check for empty level (unused multi-part) ---------- */
377 if (!reading_multipart_level && is_multipart_level && !is_first_part)
379 /* this is a part of a multi-part level, but not the first part
380 (and we are not already reading parts of a multi-part level);
381 in this case, use an empty level instead of the single part */
383 use_empty_level = TRUE;
388 /* ---------- check for finished multi-part level ---------- */
390 if (reading_multipart_level &&
391 (!is_multipart_level ||
392 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
395 /* we are already reading parts of a multi-part level, but this level is
396 either not a multi-part level, or a part of a different multi-part
397 level; in both cases, the multi-part level seems to be complete */
402 /* ---------- here we have one part of a multi-part level ---------- */
404 reading_multipart_level = TRUE;
406 if (is_first_part) /* start with first part of new multi-part level */
408 /* copy level info structure from first part */
409 multipart_level = native_sp_level;
411 /* clear playfield of new multi-part level */
412 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
413 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
414 multipart_level.playfield[x][y] = fiSpace;
417 if (name_first == '?')
419 if (name_last == '?')
422 multipart_xpos = (int)(name_first - '0');
423 multipart_ypos = (int)(name_last - '0');
426 printf("----------> part (%d/%d) of multi-part level '%s'\n",
427 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
430 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
431 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
433 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
438 multipart_level.width = MAX(multipart_level.width,
439 multipart_xpos * SP_PLAYFIELD_WIDTH);
440 multipart_level.height = MAX(multipart_level.height,
441 multipart_ypos * SP_PLAYFIELD_HEIGHT);
443 /* copy level part at the right position of multi-part level */
444 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
446 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
448 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
449 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
451 multipart_level.playfield[start_x + x][start_y + y] =
452 native_sp_level.playfield[x][y];
461 setLevelInfoToDefaults_SP();
463 Error(ERR_WARN, "single part of multi-part level -- using empty level");
466 if (reading_multipart_level)
467 native_sp_level = multipart_level;
469 copyInternalEngineVars_SP();