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;
104 RandomSeed = LInfo.DemoRandomSeed;
109 static void LoadNativeLevelFromFileStream_SP(FILE *file, boolean demo_available)
111 LevelInfoType *header = &native_sp_level.header;
114 /* for details of the Supaplex level format, see Herman Perk's Supaplex
115 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
117 native_sp_level.width = SP_PLAYFIELD_WIDTH;
118 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
120 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
121 for (y = 0; y < native_sp_level.height; y++)
122 for (x = 0; x < native_sp_level.width; x++)
123 native_sp_level.playfield[x][y] = getFile8Bit(file);
125 /* read level header (96 bytes) */
127 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
129 /* initial gravity: 1 == "on", anything else (0) == "off" */
130 header->InitialGravity = getFile8Bit(file);
132 /* SpeedFixVersion XOR 0x20 */
133 header->Version = getFile8Bit(file);
135 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
136 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
137 header->LevelTitle[i] = getFile8Bit(file);
139 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
140 header->InitialFreezeZonks = getFile8Bit(file);
142 /* number of infotrons needed; 0 means that Supaplex will count the total
143 amount of infotrons in the level and use the low byte of that number
144 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
145 header->InfotronsNeeded = getFile8Bit(file);
147 /* number of special ("gravity") port entries below (maximum 10 allowed) */
148 header->SpecialPortCount = getFile8Bit(file);
151 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
154 /* database of properties of up to 10 special ports (6 bytes per port) */
155 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
157 SpecialPortType *port = &header->SpecialPort[i];
159 /* high and low byte of the location of a special port; if (x, y) are the
160 coordinates of a port in the field and (0, 0) is the top-left corner,
161 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
162 of what may be expected: Supaplex works with a game field in memory
163 which is 2 bytes per tile) */
164 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
168 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
169 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
171 printf("::: %d: port_location == %d => (%d, %d)\n",
172 i, port->PortLocation, port_x, port_y);
176 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
177 port->Gravity = getFile8Bit(file);
179 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
180 port->FreezeZonks = getFile8Bit(file);
182 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
183 port->FreezeEnemies = getFile8Bit(file);
185 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
188 /* SpeedByte XOR Highbyte(RandomSeed) */
189 header->SpeedByte = getFile8Bit(file);
191 /* CheckSum XOR SpeedByte */
192 header->CheckSumByte = getFile8Bit(file);
194 /* random seed used for recorded demos */
195 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
198 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
201 /* auto-determine number of infotrons if it was stored as "0" -- see above */
202 if (header->InfotronsNeeded == 0)
204 for (x = 0; x < native_sp_level.width; x++)
205 for (y = 0; y < native_sp_level.height; y++)
206 if (native_sp_level.playfield[x][y] == fiInfotron)
207 header->InfotronsNeeded++;
209 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
212 /* also load demo tape, if available */
216 int level_nr = getFile8Bit(file);
218 level_nr &= 0x7f; /* clear highest bit */
219 level_nr = (level_nr < 1 ? 1 :
220 level_nr > 111 ? 111 : level_nr);
222 native_sp_level.demo.level_nr = level_nr;
224 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
226 native_sp_level.demo.data[i] = getFile8Bit(file);
228 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
236 native_sp_level.demo.length = i;
237 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
241 boolean LoadNativeLevel_SP(char *filename, int pos)
245 char name_first, name_last;
246 struct LevelInfo_SP multipart_level;
247 int multipart_xpos, multipart_ypos;
248 boolean is_multipart_level;
249 boolean is_first_part;
250 boolean reading_multipart_level = FALSE;
251 boolean use_empty_level = FALSE;
252 LevelInfoType *header = &native_sp_level.header;
253 boolean demo_available = (strSuffix(filename, ".sp") ||
254 strSuffix(filename, ".SP"));
256 /* always start with reliable default values */
257 setLevelInfoToDefaults_SP();
258 copyInternalEngineVars_SP();
260 if (!(file = fopen(filename, MODE_READ)))
262 Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
267 /* position file stream to the requested level (in case of level package) */
268 if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
270 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
275 /* there exist Supaplex level package files with multi-part levels which
276 can be detected as follows: instead of leading and trailing dashes ('-')
277 to pad the level name, they have leading and trailing numbers which are
278 the x and y coordinations of the current part of the multi-part level;
279 if there are '?' characters instead of numbers on the left or right side
280 of the level name, the multi-part level consists of only horizontal or
283 for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
285 LoadNativeLevelFromFileStream_SP(file, demo_available);
287 /* check if this level is a part of a bigger multi-part level */
289 name_first = header->LevelTitle[0];
290 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
293 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
294 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
297 ((name_first == '?' || name_first == '1') &&
298 (name_last == '?' || name_last == '1'));
300 /* correct leading multipart level meta information in level name */
301 for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
302 header->LevelTitle[i] = '-';
304 /* correct trailing multipart level meta information in level name */
305 for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
306 header->LevelTitle[i] = '-';
308 /* ---------- check for normal single level ---------- */
310 if (!reading_multipart_level && !is_multipart_level)
312 /* the current level is simply a normal single-part level, and we are
313 not reading a multi-part level yet, so return the level as it is */
318 /* ---------- check for empty level (unused multi-part) ---------- */
320 if (!reading_multipart_level && is_multipart_level && !is_first_part)
322 /* this is a part of a multi-part level, but not the first part
323 (and we are not already reading parts of a multi-part level);
324 in this case, use an empty level instead of the single part */
326 use_empty_level = TRUE;
331 /* ---------- check for finished multi-part level ---------- */
333 if (reading_multipart_level &&
334 (!is_multipart_level ||
335 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
338 /* we are already reading parts of a multi-part level, but this level is
339 either not a multi-part level, or a part of a different multi-part
340 level; in both cases, the multi-part level seems to be complete */
345 /* ---------- here we have one part of a multi-part level ---------- */
347 reading_multipart_level = TRUE;
349 if (is_first_part) /* start with first part of new multi-part level */
351 /* copy level info structure from first part */
352 multipart_level = native_sp_level;
354 /* clear playfield of new multi-part level */
355 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
356 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
357 multipart_level.playfield[x][y] = fiSpace;
360 if (name_first == '?')
362 if (name_last == '?')
365 multipart_xpos = (int)(name_first - '0');
366 multipart_ypos = (int)(name_last - '0');
369 printf("----------> part (%d/%d) of multi-part level '%s'\n",
370 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
373 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
374 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
376 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
381 multipart_level.width = MAX(multipart_level.width,
382 multipart_xpos * SP_PLAYFIELD_WIDTH);
383 multipart_level.height = MAX(multipart_level.height,
384 multipart_ypos * SP_PLAYFIELD_HEIGHT);
386 /* copy level part at the right position of multi-part level */
387 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
389 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
391 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
392 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
394 multipart_level.playfield[start_x + x][start_y + y] =
395 native_sp_level.playfield[x][y];
404 setLevelInfoToDefaults_SP();
406 Error(ERR_WARN, "single part of multi-part level -- using empty level");
409 if (reading_multipart_level)
410 native_sp_level = multipart_level;
412 copyInternalEngineVars_SP();