1
2 #include "main_sp.h"
3 #include "global.h"
4
5
6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level */
8 /* ------------------------------------------------------------------------- */
9
setTapeInfoToDefaults_SP()10 void setTapeInfoToDefaults_SP()
11 {
12 native_sp_level.demo.is_available = FALSE;
13 native_sp_level.demo.length = 0;
14 }
15
setLevelInfoToDefaults_SP()16 void setLevelInfoToDefaults_SP()
17 {
18 LevelInfoType *header = &native_sp_level.header;
19 char *empty_title = "-------- EMPTY --------";
20 int i, x, y;
21
22 native_sp_level.game_sp = &game_sp;
23
24 native_sp_level.width = SP_STD_PLAYFIELD_WIDTH;
25 native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
26
27 for (x = 0; x < native_sp_level.width; x++)
28 for (y = 0; y < native_sp_level.height; y++)
29 native_sp_level.playfield[x][y] = fiSpace;
30
31 /* copy string (without terminating '\0' character!) */
32 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
33 header->LevelTitle[i] = empty_title[i];
34
35 header->InitialGravity = 0;
36 header->Version = 0;
37 header->InitialFreezeZonks = 0;
38 header->InfotronsNeeded = 0;
39 header->SpecialPortCount = 0;
40 header->SpeedByte = 0;
41 header->CheckSumByte = 0;
42 header->DemoRandomSeed = 0;
43
44 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
45 {
46 SpecialPortType *port = &header->SpecialPort[i];
47
48 port->PortLocation = 0;
49 port->Gravity = 0;
50 port->FreezeZonks = 0;
51 port->FreezeEnemies = 0;
52 }
53
54 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
55 for (i = 0; i < SP_HEADER_SIZE; i++)
56 native_sp_level.header_raw_bytes[i] = 0x20;
57
58 setTapeInfoToDefaults_SP();
59 }
60
copyInternalEngineVars_SP()61 void copyInternalEngineVars_SP()
62 {
63 char *preceding_playfield_memory[] =
64 {
65 "95 89 95 89 95 89 3b 8a 3b 8a 3b 8a 3b 8a 3b 8a", // |......;.;.;.;.;.|
66 "3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a e8 8a e8 8a", // |;.;.;.�.�.�.�.�.|
67 "e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b b1 8b b1 8b", // |�.�.�.�.�.�.�.�.|
68 "b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c 85 8c 85 8c", // |�.�.�...........|
69 "85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d 5b 8d 5b 8d", // |......[.[.[.[.[.|
70 "5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e 06 8e 06 8e", // |[.[.[...........|
71 "06 8e 06 8e 06 8e ac 8e ac 8e ac 8e ac 8e ac 8e", // |......�.�.�.�.�.|
72 "ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f 59 8f 59 8f", // |�.�.�.Y.Y.Y.Y.Y.|
73 "59 8f 59 8f 59 8f 00 00 70 13 00 00 00 00 e8 17", // |Y.Y.Y...p.....�.|
74 "00 00 00 00 00 00 69 38 00 00 00 00 00 00 00 00", // |......i8........|
75 "00 00 00 00 00 00 00 00 d0 86 00 00 b2 34 00 00", // |........�...�4..|
76 "00 00 00 00 00 00 8f 8b 1d 34 00 00 00 00 00 00", // |.........4......|
77 "00 00 00 00 23 39 09 09 00 0c 00 08 00 58 00 00", // |....#9.......X..|
78 "00 00 00 25 77 06 7f 00 00 00 01 00 00 00 00 00", // |...%w...........|
79 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
80 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
81 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
82 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
83 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
84 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
85 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
86 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
87 "00 00 00 00 00 00 00 00 00 ec 06 26 05 00 00 00", // |.........�.&....|
88 "00 00 00 01 00 00 00 00 31 32 33 34 35 36 37 38", // |........12345678|
89 "39 30 2d 00 08 00 51 57 45 52 54 59 55 49 4f 50", // |90-...QWERTYUIOP|
90 "00 00 0a 00 41 53 44 46 47 48 4a 4b 4c 00 00 00", // |....ASDFGHJKL...|
91 "00 00 5a 58 43 56 42 4e 4d 00 00 00 00 00 00 20", // |..ZXCVBNM...... |
92 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
93 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
94 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
95 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
96 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
97 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
98 "00 00 00 00 00 00 2e 00 1e 00 31 00 14 00 39 00", // |..........1...9.|
99 "1f 00 14 00 18 00 ff ff 01 00 01 4c 45 56 45 4c", // |......��...LEVEL|
100 "53 2e 44 41 54 00 00 00 00 00 00 00 00 00 00 00", // |S.DAT...........|
101 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
102 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
103 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
104 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
105 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
106 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
107 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................|
108
109 NULL
110 };
111 int preceding_buffer_size = 0;
112 int count;
113 int i, x, y;
114
115 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
116 preceding_buffer_size += 8; /* eight 16-bit integer values */
117
118 /* needed for engine snapshots */
119 game_sp.preceding_buffer_size = preceding_buffer_size;
120
121 LInfo = native_sp_level.header;
122
123 FieldWidth = native_sp_level.width;
124 FieldHeight = native_sp_level.height;
125 HeaderSize = 96;
126
127 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
128 LevelMax = (FieldWidth * FieldHeight) - 1;
129
130 /* (add one byte for the level number stored as first byte of demo data) */
131 FileMax = FieldMax + native_sp_level.demo.length + 1;
132
133 #if 0
134 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
135 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
136 PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax);
137 #endif
138
139 count = 0;
140 for (i = 0; preceding_playfield_memory[i] != NULL; i++)
141 {
142 char *s = preceding_playfield_memory[i];
143 boolean hi_byte = FALSE; /* little endian data => start with low byte */
144
145 while (s[0] != '\0' && s[1] != '\0')
146 {
147 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
148 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
149 int byte = (hi_nibble << 4) | lo_nibble;
150
151 if (hi_byte)
152 byte <<= 8;
153
154 PlayField16[-preceding_buffer_size + count] |= byte;
155
156 if (hi_byte)
157 count++;
158
159 hi_byte = !hi_byte;
160
161 s += 2;
162
163 while (*s == ' ')
164 s++;
165 }
166 }
167
168 count = 0;
169 for (y = 0; y < native_sp_level.height; y++)
170 for (x = 0; x < native_sp_level.width; x++)
171 PlayField8[count++] = native_sp_level.playfield[x][y];
172
173 /* add raw header bytes to subsequent playfield buffer zone */
174 for (i = 0; i < SP_HEADER_SIZE; i++)
175 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
176
177 for (i = 0; i < count; i++)
178 {
179 PlayField16[i] = PlayField8[i];
180 DisPlayField[i] = PlayField8[i];
181 PlayField8[i] = 0;
182 }
183
184 if (native_sp_level.demo.is_available)
185 {
186 DemoAvailable = True;
187
188 #if 0
189 /* !!! NEVER USED !!! */
190 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
191
192 /* !!! NEVER USED !!! */
193 for (i = 0; i < native_sp_level.demo.length; i++)
194 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
195 #endif
196 }
197
198 #if 0
199 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
200 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
201 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
202 #endif
203
204 GravityFlag = LInfo.InitialGravity;
205 FreezeZonks = LInfo.InitialFreezeZonks;
206
207 #if 1
208 /* this is set by main game tape code to native random generator directly */
209 #else
210 RandomSeed = LInfo.DemoRandomSeed;
211 #endif
212
213 LevelLoaded = True;
214 }
215
LoadNativeLevelFromFileStream_SP(FILE * file,int width,int height,boolean demo_available)216 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
217 boolean demo_available)
218 {
219 LevelInfoType *header = &native_sp_level.header;
220 int i, x, y;
221
222 /* for details of the Supaplex level format, see Herman Perk's Supaplex
223 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
224
225 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
226 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
227
228 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
229 /* (MPX levels may have non-standard playfield size -- check max. size) */
230 for (y = 0; y < height; y++)
231 {
232 for (x = 0; x < width; x++)
233 {
234 byte element = getFile8Bit(file);
235
236 if (x < SP_MAX_PLAYFIELD_WIDTH &&
237 y < SP_MAX_PLAYFIELD_HEIGHT)
238 native_sp_level.playfield[x][y] = element;
239 }
240 }
241
242 /* read level header (96 bytes) */
243
244 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
245
246 /* initial gravity: 1 == "on", anything else (0) == "off" */
247 header->InitialGravity = getFile8Bit(file);
248
249 /* SpeedFixVersion XOR 0x20 */
250 header->Version = getFile8Bit(file);
251
252 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
253 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
254 header->LevelTitle[i] = getFile8Bit(file);
255
256 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
257 header->InitialFreezeZonks = getFile8Bit(file);
258
259 /* number of infotrons needed; 0 means that Supaplex will count the total
260 amount of infotrons in the level and use the low byte of that number
261 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
262 header->InfotronsNeeded = getFile8Bit(file);
263
264 /* number of special ("gravity") port entries below (maximum 10 allowed) */
265 header->SpecialPortCount = getFile8Bit(file);
266
267 /* database of properties of up to 10 special ports (6 bytes per port) */
268 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
269 {
270 SpecialPortType *port = &header->SpecialPort[i];
271
272 /* high and low byte of the location of a special port; if (x, y) are the
273 coordinates of a port in the field and (0, 0) is the top-left corner,
274 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
275 of what may be expected: Supaplex works with a game field in memory
276 which is 2 bytes per tile) */
277 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
278
279 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
280 port->Gravity = getFile8Bit(file);
281
282 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
283 port->FreezeZonks = getFile8Bit(file);
284
285 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
286 port->FreezeEnemies = getFile8Bit(file);
287
288 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
289 }
290
291 /* SpeedByte XOR Highbyte(RandomSeed) */
292 header->SpeedByte = getFile8Bit(file);
293
294 /* CheckSum XOR SpeedByte */
295 header->CheckSumByte = getFile8Bit(file);
296
297 /* random seed used for recorded demos */
298 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
299
300 /* auto-determine number of infotrons if it was stored as "0" -- see above */
301 if (header->InfotronsNeeded == 0)
302 {
303 for (x = 0; x < native_sp_level.width; x++)
304 for (y = 0; y < native_sp_level.height; y++)
305 if (native_sp_level.playfield[x][y] == fiInfotron)
306 header->InfotronsNeeded++;
307
308 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
309 }
310
311 /* read raw level header bytes (96 bytes) */
312
313 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
314 for (i = 0; i < SP_HEADER_SIZE; i++)
315 native_sp_level.header_raw_bytes[i] = fgetc(file);
316
317 /* also load demo tape, if available (only in single level files) */
318
319 if (demo_available)
320 {
321 int level_nr = getFile8Bit(file);
322
323 level_nr &= 0x7f; /* clear highest bit */
324 level_nr = (level_nr < 1 ? 1 :
325 level_nr > 111 ? 111 : level_nr);
326
327 native_sp_level.demo.level_nr = level_nr;
328
329 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
330 {
331 native_sp_level.demo.data[i] = getFile8Bit(file);
332
333 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
334 {
335 i++;
336
337 break;
338 }
339 }
340
341 native_sp_level.demo.length = i;
342 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
343 }
344 }
345
LoadNativeLevel_SP(char * filename,int level_pos)346 boolean LoadNativeLevel_SP(char *filename, int level_pos)
347 {
348 FILE *file;
349 int i, l, x, y;
350 char name_first, name_last;
351 struct LevelInfo_SP multipart_level;
352 int multipart_xpos, multipart_ypos;
353 boolean is_multipart_level;
354 boolean is_first_part;
355 boolean reading_multipart_level = FALSE;
356 boolean use_empty_level = FALSE;
357 LevelInfoType *header = &native_sp_level.header;
358 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
359 strSuffixLower(filename, ".mpx"));
360 boolean demo_available = is_single_level_file;
361 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
362 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
363 int level_width = SP_STD_PLAYFIELD_WIDTH;
364 int level_height = SP_STD_PLAYFIELD_HEIGHT;
365
366 /* always start with reliable default values */
367 setLevelInfoToDefaults_SP();
368 copyInternalEngineVars_SP();
369
370 if (!(file = fopen(filename, MODE_READ)))
371 {
372 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
373
374 return FALSE;
375 }
376
377 if (is_mpx_file)
378 {
379 char mpx_chunk_name[4 + 1];
380 int mpx_version;
381 int mpx_level_count;
382 LevelDescriptor *mpx_level_desc;
383
384 getFileChunkBE(file, mpx_chunk_name, NULL);
385
386 if (!strEqual(mpx_chunk_name, "MPX "))
387 {
388 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
389 filename);
390
391 return FALSE;
392 }
393
394 mpx_version = getFile16BitLE(file);
395
396 if (mpx_version != 1)
397 {
398 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
399 filename);
400
401 return FALSE;
402 }
403
404 mpx_level_count = getFile16BitLE(file);
405
406 if (mpx_level_count < 1)
407 {
408 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
409 filename);
410
411 return FALSE;
412 }
413
414 if (level_pos >= mpx_level_count)
415 {
416 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
417 filename);
418
419 return FALSE;
420 }
421
422 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
423
424 for (i = 0; i < mpx_level_count; i++)
425 {
426 LevelDescriptor *ldesc = &mpx_level_desc[i];
427
428 ldesc->Width = getFile16BitLE(file);
429 ldesc->Height = getFile16BitLE(file);
430 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
431 ldesc->Size = getFile32BitLE(file);
432 }
433
434 level_width = mpx_level_desc[level_pos].Width;
435 level_height = mpx_level_desc[level_pos].Height;
436
437 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
438 }
439
440 /* position file stream to the requested level (in case of level package) */
441 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
442 {
443 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
444
445 return FALSE;
446 }
447
448 /* there exist Supaplex level package files with multi-part levels which
449 can be detected as follows: instead of leading and trailing dashes ('-')
450 to pad the level name, they have leading and trailing numbers which are
451 the x and y coordinations of the current part of the multi-part level;
452 if there are '?' characters instead of numbers on the left or right side
453 of the level name, the multi-part level consists of only horizontal or
454 vertical parts */
455
456 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
457 {
458 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
459 demo_available);
460
461 /* check if this level is a part of a bigger multi-part level */
462
463 if (is_single_level_file)
464 break;
465
466 name_first = header->LevelTitle[0];
467 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
468
469 is_multipart_level =
470 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
471 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
472
473 is_first_part =
474 ((name_first == '?' || name_first == '1') &&
475 (name_last == '?' || name_last == '1'));
476
477 if (is_multipart_level)
478 {
479 /* correct leading multipart level meta information in level name */
480 for (i = 0;
481 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
482 i++)
483 header->LevelTitle[i] = '-';
484
485 /* correct trailing multipart level meta information in level name */
486 for (i = SP_LEVEL_NAME_LEN - 1;
487 i >= 0 && header->LevelTitle[i] == name_last;
488 i--)
489 header->LevelTitle[i] = '-';
490 }
491
492 /* ---------- check for normal single level ---------- */
493
494 if (!reading_multipart_level && !is_multipart_level)
495 {
496 /* the current level is simply a normal single-part level, and we are
497 not reading a multi-part level yet, so return the level as it is */
498
499 break;
500 }
501
502 /* ---------- check for empty level (unused multi-part) ---------- */
503
504 if (!reading_multipart_level && is_multipart_level && !is_first_part)
505 {
506 /* this is a part of a multi-part level, but not the first part
507 (and we are not already reading parts of a multi-part level);
508 in this case, use an empty level instead of the single part */
509
510 use_empty_level = TRUE;
511
512 break;
513 }
514
515 /* ---------- check for finished multi-part level ---------- */
516
517 if (reading_multipart_level &&
518 (!is_multipart_level ||
519 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
520 SP_LEVEL_NAME_LEN)))
521 {
522 /* we are already reading parts of a multi-part level, but this level is
523 either not a multi-part level, or a part of a different multi-part
524 level; in both cases, the multi-part level seems to be complete */
525
526 break;
527 }
528
529 /* ---------- here we have one part of a multi-part level ---------- */
530
531 reading_multipart_level = TRUE;
532
533 if (is_first_part) /* start with first part of new multi-part level */
534 {
535 /* copy level info structure from first part */
536 multipart_level = native_sp_level;
537
538 /* clear playfield of new multi-part level */
539 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
540 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
541 multipart_level.playfield[x][y] = fiSpace;
542 }
543
544 if (name_first == '?')
545 name_first = '1';
546 if (name_last == '?')
547 name_last = '1';
548
549 multipart_xpos = (int)(name_first - '0');
550 multipart_ypos = (int)(name_last - '0');
551
552 #if 0
553 printf("----------> part (%d/%d) of multi-part level '%s'\n",
554 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
555 #endif
556
557 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
558 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
559 {
560 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
561
562 break;
563 }
564
565 multipart_level.width = MAX(multipart_level.width,
566 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
567 multipart_level.height = MAX(multipart_level.height,
568 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
569
570 /* copy level part at the right position of multi-part level */
571 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
572 {
573 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
574 {
575 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
576 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
577
578 multipart_level.playfield[start_x + x][start_y + y] =
579 native_sp_level.playfield[x][y];
580 }
581 }
582 }
583
584 fclose(file);
585
586 if (use_empty_level)
587 {
588 setLevelInfoToDefaults_SP();
589
590 Error(ERR_WARN, "single part of multi-part level -- using empty level");
591 }
592
593 if (reading_multipart_level)
594 native_sp_level = multipart_level;
595
596 copyInternalEngineVars_SP();
597
598 return TRUE;
599 }
600
SaveNativeLevel_SP(char * filename)601 void SaveNativeLevel_SP(char *filename)
602 {
603 LevelInfoType *header = &native_sp_level.header;
604 FILE *file;
605 int i, x, y;
606
607 if (!(file = fopen(filename, MODE_WRITE)))
608 {
609 Error(ERR_WARN, "cannot save native level file '%s'", filename);
610
611 return;
612 }
613
614 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
615 for (y = 0; y < native_sp_level.height; y++)
616 for (x = 0; x < native_sp_level.width; x++)
617 putFile8Bit(file, native_sp_level.playfield[x][y]);
618
619 /* write level header (96 bytes) */
620
621 WriteUnusedBytesToFile(file, 4);
622
623 putFile8Bit(file, header->InitialGravity);
624 putFile8Bit(file, header->Version);
625
626 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
627 putFile8Bit(file, header->LevelTitle[i]);
628
629 putFile8Bit(file, header->InitialFreezeZonks);
630 putFile8Bit(file, header->InfotronsNeeded);
631 putFile8Bit(file, header->SpecialPortCount);
632
633 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
634 {
635 SpecialPortType *port = &header->SpecialPort[i];
636
637 putFile16BitBE(file, port->PortLocation);
638 putFile8Bit(file, port->Gravity);
639 putFile8Bit(file, port->FreezeZonks);
640 putFile8Bit(file, port->FreezeEnemies);
641
642 WriteUnusedBytesToFile(file, 1);
643 }
644
645 putFile8Bit(file, header->SpeedByte);
646 putFile8Bit(file, header->CheckSumByte);
647 putFile16BitLE(file, header->DemoRandomSeed);
648
649 /* also save demo tape, if available */
650
651 if (native_sp_level.demo.is_available)
652 {
653 putFile8Bit(file, native_sp_level.demo.level_nr);
654
655 for (i = 0; i < native_sp_level.demo.length; i++)
656 putFile8Bit(file, native_sp_level.demo.data[i]);
657 }
658
659 fclose(file);
660 }
661