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 for (y = 0; y < native_sp_level.height; y++)
78 for (x = 0; x < native_sp_level.width; x++)
79 PlayField8[count++] = native_sp_level.playfield[x][y];
81 /* add raw header bytes to subsequent playfield buffer zone */
82 for (i = 0; i < SP_HEADER_SIZE; i++)
83 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
85 for (i = 0; i < count; i++)
87 PlayField16[i] = PlayField8[i];
88 DisPlayField[i] = PlayField8[i];
94 for (i = 0; y = 0; y < native_sp_level.height; y++)
96 for (x = 0; x < native_sp_level.width; x++)
98 PlayField8[i] = native_sp_level.playfield[x][y];
100 PlayField16[i] = PlayField8[i];
101 DisPlayField[i] = PlayField8[i];
110 if (native_sp_level.demo.is_available)
112 DemoAvailable = True;
114 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
116 for (i = 0; i < native_sp_level.demo.length; i++)
117 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
120 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
121 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
122 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
124 DemoPointer = FieldMax + 1;
125 DemoOffset = DemoPointer;
126 DemoKeyRepeatCounter = 0;
128 GravityFlag = LInfo.InitialGravity;
129 FreezeZonks = LInfo.InitialFreezeZonks;
132 /* this is set by main game tape code to native random generator directly */
136 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
139 RandomSeed = LInfo.DemoRandomSeed;
146 static void LoadNativeLevelFromFileStream_SP(FILE *file, boolean demo_available)
148 LevelInfoType *header = &native_sp_level.header;
151 /* for details of the Supaplex level format, see Herman Perk's Supaplex
152 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
154 native_sp_level.width = SP_PLAYFIELD_WIDTH;
155 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
157 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
158 for (y = 0; y < native_sp_level.height; y++)
159 for (x = 0; x < native_sp_level.width; x++)
160 native_sp_level.playfield[x][y] = getFile8Bit(file);
162 /* read level header (96 bytes) */
164 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
166 /* initial gravity: 1 == "on", anything else (0) == "off" */
167 header->InitialGravity = getFile8Bit(file);
169 /* SpeedFixVersion XOR 0x20 */
170 header->Version = getFile8Bit(file);
172 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
173 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
174 header->LevelTitle[i] = getFile8Bit(file);
176 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
177 header->InitialFreezeZonks = getFile8Bit(file);
179 /* number of infotrons needed; 0 means that Supaplex will count the total
180 amount of infotrons in the level and use the low byte of that number
181 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
182 header->InfotronsNeeded = getFile8Bit(file);
184 /* number of special ("gravity") port entries below (maximum 10 allowed) */
185 header->SpecialPortCount = getFile8Bit(file);
188 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
191 /* database of properties of up to 10 special ports (6 bytes per port) */
192 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
194 SpecialPortType *port = &header->SpecialPort[i];
196 /* high and low byte of the location of a special port; if (x, y) are the
197 coordinates of a port in the field and (0, 0) is the top-left corner,
198 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
199 of what may be expected: Supaplex works with a game field in memory
200 which is 2 bytes per tile) */
201 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
205 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
206 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
208 printf("::: %d: port_location == %d => (%d, %d)\n",
209 i, port->PortLocation, port_x, port_y);
213 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
214 port->Gravity = getFile8Bit(file);
216 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
217 port->FreezeZonks = getFile8Bit(file);
219 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
220 port->FreezeEnemies = getFile8Bit(file);
222 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
225 /* SpeedByte XOR Highbyte(RandomSeed) */
226 header->SpeedByte = getFile8Bit(file);
228 /* CheckSum XOR SpeedByte */
229 header->CheckSumByte = getFile8Bit(file);
231 /* random seed used for recorded demos */
232 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
233 // header->DemoRandomSeed = getFile16BitBE(file); /* !!! TEST ONLY !!! */
236 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
239 /* auto-determine number of infotrons if it was stored as "0" -- see above */
240 if (header->InfotronsNeeded == 0)
242 for (x = 0; x < native_sp_level.width; x++)
243 for (y = 0; y < native_sp_level.height; y++)
244 if (native_sp_level.playfield[x][y] == fiInfotron)
245 header->InfotronsNeeded++;
247 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
250 /* read raw level header bytes (96 bytes) */
252 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
253 for (i = 0; i < SP_HEADER_SIZE; i++)
254 native_sp_level.header_raw_bytes[i] = fgetc(file);
256 /* also load demo tape, if available */
260 int level_nr = getFile8Bit(file);
262 level_nr &= 0x7f; /* clear highest bit */
263 level_nr = (level_nr < 1 ? 1 :
264 level_nr > 111 ? 111 : level_nr);
266 native_sp_level.demo.level_nr = level_nr;
268 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
270 native_sp_level.demo.data[i] = getFile8Bit(file);
272 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
280 native_sp_level.demo.length = i;
281 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
285 boolean LoadNativeLevel_SP(char *filename, int pos)
289 char name_first, name_last;
290 struct LevelInfo_SP multipart_level;
291 int multipart_xpos, multipart_ypos;
292 boolean is_multipart_level;
293 boolean is_first_part;
294 boolean reading_multipart_level = FALSE;
295 boolean use_empty_level = FALSE;
296 LevelInfoType *header = &native_sp_level.header;
297 boolean demo_available = (strSuffix(filename, ".sp") ||
298 strSuffix(filename, ".SP"));
300 /* always start with reliable default values */
301 setLevelInfoToDefaults_SP();
302 copyInternalEngineVars_SP();
304 if (!(file = fopen(filename, MODE_READ)))
306 Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
311 /* position file stream to the requested level (in case of level package) */
312 if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
314 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
319 /* there exist Supaplex level package files with multi-part levels which
320 can be detected as follows: instead of leading and trailing dashes ('-')
321 to pad the level name, they have leading and trailing numbers which are
322 the x and y coordinations of the current part of the multi-part level;
323 if there are '?' characters instead of numbers on the left or right side
324 of the level name, the multi-part level consists of only horizontal or
327 for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
329 LoadNativeLevelFromFileStream_SP(file, demo_available);
331 /* check if this level is a part of a bigger multi-part level */
333 name_first = header->LevelTitle[0];
334 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
337 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
338 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
341 ((name_first == '?' || name_first == '1') &&
342 (name_last == '?' || name_last == '1'));
344 /* correct leading multipart level meta information in level name */
345 for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
346 header->LevelTitle[i] = '-';
348 /* correct trailing multipart level meta information in level name */
349 for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
350 header->LevelTitle[i] = '-';
352 /* ---------- check for normal single level ---------- */
354 if (!reading_multipart_level && !is_multipart_level)
356 /* the current level is simply a normal single-part level, and we are
357 not reading a multi-part level yet, so return the level as it is */
362 /* ---------- check for empty level (unused multi-part) ---------- */
364 if (!reading_multipart_level && is_multipart_level && !is_first_part)
366 /* this is a part of a multi-part level, but not the first part
367 (and we are not already reading parts of a multi-part level);
368 in this case, use an empty level instead of the single part */
370 use_empty_level = TRUE;
375 /* ---------- check for finished multi-part level ---------- */
377 if (reading_multipart_level &&
378 (!is_multipart_level ||
379 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
382 /* we are already reading parts of a multi-part level, but this level is
383 either not a multi-part level, or a part of a different multi-part
384 level; in both cases, the multi-part level seems to be complete */
389 /* ---------- here we have one part of a multi-part level ---------- */
391 reading_multipart_level = TRUE;
393 if (is_first_part) /* start with first part of new multi-part level */
395 /* copy level info structure from first part */
396 multipart_level = native_sp_level;
398 /* clear playfield of new multi-part level */
399 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
400 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
401 multipart_level.playfield[x][y] = fiSpace;
404 if (name_first == '?')
406 if (name_last == '?')
409 multipart_xpos = (int)(name_first - '0');
410 multipart_ypos = (int)(name_last - '0');
413 printf("----------> part (%d/%d) of multi-part level '%s'\n",
414 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
417 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
418 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
420 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
425 multipart_level.width = MAX(multipart_level.width,
426 multipart_xpos * SP_PLAYFIELD_WIDTH);
427 multipart_level.height = MAX(multipart_level.height,
428 multipart_ypos * SP_PLAYFIELD_HEIGHT);
430 /* copy level part at the right position of multi-part level */
431 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
433 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
435 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
436 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
438 multipart_level.playfield[start_x + x][start_y + y] =
439 native_sp_level.playfield[x][y];
448 setLevelInfoToDefaults_SP();
450 Error(ERR_WARN, "single part of multi-part level -- using empty level");
453 if (reading_multipart_level)
454 native_sp_level = multipart_level;
456 copyInternalEngineVars_SP();