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