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