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 native_sp_level.demo.is_available = FALSE;
47 native_sp_level.demo.length = 0;
50 void copyInternalEngineVars_SP()
54 LInfo = native_sp_level.header;
56 FieldWidth = native_sp_level.width;
57 FieldHeight = native_sp_level.height;
60 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
61 LevelMax = (FieldWidth * FieldHeight) - 1;
63 FileMax = FieldMax + native_sp_level.demo.length;
65 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
66 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
67 PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
69 for (i = 0, y = 0; y < native_sp_level.height; y++)
71 for (x = 0; x < native_sp_level.width; x++)
73 PlayField8[i] = native_sp_level.playfield[x][y];
75 PlayField16[i] = PlayField8[i];
76 DisPlayField[i] = PlayField8[i];
83 if (native_sp_level.demo.is_available)
87 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
89 for (i = 0; i < native_sp_level.demo.length; i++)
90 PlayField8[FieldMax + i + 2] = native_sp_level.demo.data[i];
93 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
94 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
95 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
97 DemoPointer = FieldMax + 1;
98 DemoOffset = DemoPointer;
99 DemoKeyRepeatCounter = 0;
101 GravityFlag = LInfo.InitialGravity;
102 FreezeZonks = LInfo.InitialFreezeZonks;
105 /* set by main game tape code directly */
109 printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
112 RandomSeed = LInfo.DemoRandomSeed;
119 static void LoadNativeLevelFromFileStream_SP(FILE *file, boolean demo_available)
121 LevelInfoType *header = &native_sp_level.header;
124 /* for details of the Supaplex level format, see Herman Perk's Supaplex
125 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
127 native_sp_level.width = SP_PLAYFIELD_WIDTH;
128 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
130 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
131 for (y = 0; y < native_sp_level.height; y++)
132 for (x = 0; x < native_sp_level.width; x++)
133 native_sp_level.playfield[x][y] = getFile8Bit(file);
135 /* read level header (96 bytes) */
137 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
139 /* initial gravity: 1 == "on", anything else (0) == "off" */
140 header->InitialGravity = getFile8Bit(file);
142 /* SpeedFixVersion XOR 0x20 */
143 header->Version = getFile8Bit(file);
145 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
146 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
147 header->LevelTitle[i] = getFile8Bit(file);
149 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
150 header->InitialFreezeZonks = getFile8Bit(file);
152 /* number of infotrons needed; 0 means that Supaplex will count the total
153 amount of infotrons in the level and use the low byte of that number
154 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
155 header->InfotronsNeeded = getFile8Bit(file);
157 /* number of special ("gravity") port entries below (maximum 10 allowed) */
158 header->SpecialPortCount = getFile8Bit(file);
161 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
164 /* database of properties of up to 10 special ports (6 bytes per port) */
165 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
167 SpecialPortType *port = &header->SpecialPort[i];
169 /* high and low byte of the location of a special port; if (x, y) are the
170 coordinates of a port in the field and (0, 0) is the top-left corner,
171 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
172 of what may be expected: Supaplex works with a game field in memory
173 which is 2 bytes per tile) */
174 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
178 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
179 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
181 printf("::: %d: port_location == %d => (%d, %d)\n",
182 i, port->PortLocation, port_x, port_y);
186 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
187 port->Gravity = getFile8Bit(file);
189 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
190 port->FreezeZonks = getFile8Bit(file);
192 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
193 port->FreezeEnemies = getFile8Bit(file);
195 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
198 /* SpeedByte XOR Highbyte(RandomSeed) */
199 header->SpeedByte = getFile8Bit(file);
201 /* CheckSum XOR SpeedByte */
202 header->CheckSumByte = getFile8Bit(file);
204 /* random seed used for recorded demos */
205 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
208 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
211 /* auto-determine number of infotrons if it was stored as "0" -- see above */
212 if (header->InfotronsNeeded == 0)
214 for (x = 0; x < native_sp_level.width; x++)
215 for (y = 0; y < native_sp_level.height; y++)
216 if (native_sp_level.playfield[x][y] == fiInfotron)
217 header->InfotronsNeeded++;
219 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
222 /* also load demo tape, if available */
226 int level_nr = getFile8Bit(file);
228 level_nr &= 0x7f; /* clear highest bit */
229 level_nr = (level_nr < 1 ? 1 :
230 level_nr > 111 ? 111 : level_nr);
232 native_sp_level.demo.level_nr = level_nr;
234 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
236 native_sp_level.demo.data[i] = getFile8Bit(file);
238 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
246 native_sp_level.demo.length = i;
247 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
251 boolean LoadNativeLevel_SP(char *filename, int pos)
255 char name_first, name_last;
256 struct LevelInfo_SP multipart_level;
257 int multipart_xpos, multipart_ypos;
258 boolean is_multipart_level;
259 boolean is_first_part;
260 boolean reading_multipart_level = FALSE;
261 boolean use_empty_level = FALSE;
262 LevelInfoType *header = &native_sp_level.header;
263 boolean demo_available = (strSuffix(filename, ".sp") ||
264 strSuffix(filename, ".SP"));
266 /* always start with reliable default values */
267 setLevelInfoToDefaults_SP();
268 copyInternalEngineVars_SP();
270 if (!(file = fopen(filename, MODE_READ)))
272 Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
277 /* position file stream to the requested level (in case of level package) */
278 if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
280 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
285 /* there exist Supaplex level package files with multi-part levels which
286 can be detected as follows: instead of leading and trailing dashes ('-')
287 to pad the level name, they have leading and trailing numbers which are
288 the x and y coordinations of the current part of the multi-part level;
289 if there are '?' characters instead of numbers on the left or right side
290 of the level name, the multi-part level consists of only horizontal or
293 for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
295 LoadNativeLevelFromFileStream_SP(file, demo_available);
297 /* check if this level is a part of a bigger multi-part level */
299 name_first = header->LevelTitle[0];
300 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
303 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
304 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
307 ((name_first == '?' || name_first == '1') &&
308 (name_last == '?' || name_last == '1'));
310 /* correct leading multipart level meta information in level name */
311 for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
312 header->LevelTitle[i] = '-';
314 /* correct trailing multipart level meta information in level name */
315 for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
316 header->LevelTitle[i] = '-';
318 /* ---------- check for normal single level ---------- */
320 if (!reading_multipart_level && !is_multipart_level)
322 /* the current level is simply a normal single-part level, and we are
323 not reading a multi-part level yet, so return the level as it is */
328 /* ---------- check for empty level (unused multi-part) ---------- */
330 if (!reading_multipart_level && is_multipart_level && !is_first_part)
332 /* this is a part of a multi-part level, but not the first part
333 (and we are not already reading parts of a multi-part level);
334 in this case, use an empty level instead of the single part */
336 use_empty_level = TRUE;
341 /* ---------- check for finished multi-part level ---------- */
343 if (reading_multipart_level &&
344 (!is_multipart_level ||
345 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
348 /* we are already reading parts of a multi-part level, but this level is
349 either not a multi-part level, or a part of a different multi-part
350 level; in both cases, the multi-part level seems to be complete */
355 /* ---------- here we have one part of a multi-part level ---------- */
357 reading_multipart_level = TRUE;
359 if (is_first_part) /* start with first part of new multi-part level */
361 /* copy level info structure from first part */
362 multipart_level = native_sp_level;
364 /* clear playfield of new multi-part level */
365 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
366 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
367 multipart_level.playfield[x][y] = fiSpace;
370 if (name_first == '?')
372 if (name_last == '?')
375 multipart_xpos = (int)(name_first - '0');
376 multipart_ypos = (int)(name_last - '0');
379 printf("----------> part (%d/%d) of multi-part level '%s'\n",
380 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
383 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
384 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
386 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
391 multipart_level.width = MAX(multipart_level.width,
392 multipart_xpos * SP_PLAYFIELD_WIDTH);
393 multipart_level.height = MAX(multipart_level.height,
394 multipart_ypos * SP_PLAYFIELD_HEIGHT);
396 /* copy level part at the right position of multi-part level */
397 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
399 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
401 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
402 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
404 multipart_level.playfield[start_x + x][start_y + y] =
405 native_sp_level.playfield[x][y];
414 setLevelInfoToDefaults_SP();
416 Error(ERR_WARN, "single part of multi-part level -- using empty level");
419 if (reading_multipart_level)
420 native_sp_level = multipart_level;
422 copyInternalEngineVars_SP();