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