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_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_available)
87 for (i = 0; i < native_sp_level.demo_length; i++)
88 PlayField8[FieldMax + i + 1] = native_sp_level.demo[i];
91 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
92 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
93 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
95 DemoPointer = FieldMax + 1;
96 DemoOffset = DemoPointer;
97 DemoKeyRepeatCounter = 0;
99 GravityFlag = LInfo.InitialGravity;
100 FreezeZonks = LInfo.InitialFreezeZonks;
102 RandomSeed = LInfo.DemoRandomSeed;
107 static void LoadNativeLevelFromFileStream_SP(FILE *file, boolean demo_available)
109 LevelInfoType *header = &native_sp_level.header;
112 /* for details of the Supaplex level format, see Herman Perk's Supaplex
113 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
115 native_sp_level.width = SP_PLAYFIELD_WIDTH;
116 native_sp_level.height = SP_PLAYFIELD_HEIGHT;
118 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
119 for (y = 0; y < native_sp_level.height; y++)
120 for (x = 0; x < native_sp_level.width; x++)
121 native_sp_level.playfield[x][y] = getFile8Bit(file);
123 /* read level header (96 bytes) */
125 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
127 /* initial gravity: 1 == "on", anything else (0) == "off" */
128 header->InitialGravity = getFile8Bit(file);
130 /* SpeedFixVersion XOR 0x20 */
131 header->Version = getFile8Bit(file);
133 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
134 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
135 header->LevelTitle[i] = getFile8Bit(file);
137 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
138 header->InitialFreezeZonks = getFile8Bit(file);
140 /* number of infotrons needed; 0 means that Supaplex will count the total
141 amount of infotrons in the level and use the low byte of that number
142 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
143 header->InfotronsNeeded = getFile8Bit(file);
145 /* number of special ("gravity") port entries below (maximum 10 allowed) */
146 header->SpecialPortCount = getFile8Bit(file);
149 printf("::: num_special_ports == %d\n", header->SpecialPortCount);
152 /* database of properties of up to 10 special ports (6 bytes per port) */
153 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
155 SpecialPortType *port = &header->SpecialPort[i];
157 /* high and low byte of the location of a special port; if (x, y) are the
158 coordinates of a port in the field and (0, 0) is the top-left corner,
159 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
160 of what may be expected: Supaplex works with a game field in memory
161 which is 2 bytes per tile) */
162 port->PortLocation = getFile16BitBE(file);
166 int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
167 int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
169 printf("::: %d: port_location == %d => (%d, %d)\n",
170 i, port->PortLocation, port_x, port_y);
174 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
175 port->Gravity = getFile8Bit(file);
177 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
178 port->FreezeZonks = getFile8Bit(file);
180 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
181 port->FreezeEnemies = getFile8Bit(file);
183 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
186 /* SpeedByte XOR Highbyte(RandomSeed) */
187 header->SpeedByte = getFile8Bit(file);
189 /* CheckSum XOR SpeedByte */
190 header->CheckSumByte = getFile8Bit(file);
192 /* random seed used for recorded demos */
193 header->DemoRandomSeed = getFile16BitLE(file);
196 printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
199 /* auto-determine number of infotrons if it was stored as "0" -- see above */
200 if (header->InfotronsNeeded == 0)
202 for (x = 0; x < native_sp_level.width; x++)
203 for (y = 0; y < native_sp_level.height; y++)
204 if (native_sp_level.playfield[x][y] == fiInfotron)
205 header->InfotronsNeeded++;
207 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
210 /* also load demo tape, if available */
214 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
216 native_sp_level.demo[i] = getFile8Bit(file);
218 if (native_sp_level.demo[i] == 0xff) /* "end of demo" byte */
226 native_sp_level.demo_length = i;
227 native_sp_level.demo_available = (native_sp_level.demo_length > 0);
231 boolean LoadNativeLevel_SP(char *filename, int pos)
235 char name_first, name_last;
236 struct LevelInfo_SP multipart_level;
237 int multipart_xpos, multipart_ypos;
238 boolean is_multipart_level;
239 boolean is_first_part;
240 boolean reading_multipart_level = FALSE;
241 boolean use_empty_level = FALSE;
242 LevelInfoType *header = &native_sp_level.header;
243 boolean demo_available = (strSuffix(filename, ".sp") ||
244 strSuffix(filename, ".SP"));
246 /* always start with reliable default values */
247 setLevelInfoToDefaults_SP();
248 copyInternalEngineVars_SP();
250 if (!(file = fopen(filename, MODE_READ)))
252 Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
257 /* position file stream to the requested level (in case of level package) */
258 if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
260 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
265 /* there exist Supaplex level package files with multi-part levels which
266 can be detected as follows: instead of leading and trailing dashes ('-')
267 to pad the level name, they have leading and trailing numbers which are
268 the x and y coordinations of the current part of the multi-part level;
269 if there are '?' characters instead of numbers on the left or right side
270 of the level name, the multi-part level consists of only horizontal or
273 for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
275 LoadNativeLevelFromFileStream_SP(file, demo_available);
277 /* check if this level is a part of a bigger multi-part level */
279 name_first = header->LevelTitle[0];
280 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
283 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
284 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
287 ((name_first == '?' || name_first == '1') &&
288 (name_last == '?' || name_last == '1'));
290 /* correct leading multipart level meta information in level name */
291 for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
292 header->LevelTitle[i] = '-';
294 /* correct trailing multipart level meta information in level name */
295 for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
296 header->LevelTitle[i] = '-';
298 /* ---------- check for normal single level ---------- */
300 if (!reading_multipart_level && !is_multipart_level)
302 /* the current level is simply a normal single-part level, and we are
303 not reading a multi-part level yet, so return the level as it is */
308 /* ---------- check for empty level (unused multi-part) ---------- */
310 if (!reading_multipart_level && is_multipart_level && !is_first_part)
312 /* this is a part of a multi-part level, but not the first part
313 (and we are not already reading parts of a multi-part level);
314 in this case, use an empty level instead of the single part */
316 use_empty_level = TRUE;
321 /* ---------- check for finished multi-part level ---------- */
323 if (reading_multipart_level &&
324 (!is_multipart_level ||
325 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
328 /* we are already reading parts of a multi-part level, but this level is
329 either not a multi-part level, or a part of a different multi-part
330 level; in both cases, the multi-part level seems to be complete */
335 /* ---------- here we have one part of a multi-part level ---------- */
337 reading_multipart_level = TRUE;
339 if (is_first_part) /* start with first part of new multi-part level */
341 /* copy level info structure from first part */
342 multipart_level = native_sp_level;
344 /* clear playfield of new multi-part level */
345 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
346 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
347 multipart_level.playfield[x][y] = fiSpace;
350 if (name_first == '?')
352 if (name_last == '?')
355 multipart_xpos = (int)(name_first - '0');
356 multipart_ypos = (int)(name_last - '0');
359 printf("----------> part (%d/%d) of multi-part level '%s'\n",
360 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
363 if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
364 multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
366 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
371 multipart_level.width = MAX(multipart_level.width,
372 multipart_xpos * SP_PLAYFIELD_WIDTH);
373 multipart_level.height = MAX(multipart_level.height,
374 multipart_ypos * SP_PLAYFIELD_HEIGHT);
376 /* copy level part at the right position of multi-part level */
377 for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
379 for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
381 int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
382 int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
384 multipart_level.playfield[start_x + x][start_y + y] =
385 native_sp_level.playfield[x][y];
394 setLevelInfoToDefaults_SP();
396 Error(ERR_WARN, "single part of multi-part level -- using empty level");
399 if (reading_multipart_level)
400 native_sp_level = multipart_level;
402 copyInternalEngineVars_SP();