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