rnd-20100712-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 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                            boolean level_info_only)
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_STD_LEVEL_SIZE;
319   int level_width  = SP_STD_PLAYFIELD_WIDTH;
320   int level_height = SP_STD_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     if (!level_info_only)
329       Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
330
331     return FALSE;
332   }
333
334   if (is_mpx_file)
335   {
336     char mpx_chunk_name[4 + 1];
337     int mpx_version;
338     int mpx_level_count;
339     LevelDescriptor *mpx_level_desc;
340
341     getFileChunkBE(file, mpx_chunk_name, NULL);
342
343     if (!strEqual(mpx_chunk_name, "MPX "))
344     {
345       Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
346             filename);
347
348       return FALSE;
349     }
350
351     mpx_version = getFile16BitLE(file);
352
353     if (mpx_version != 1)
354     {
355       Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
356             filename);
357
358       return FALSE;
359     }
360
361     mpx_level_count = getFile16BitLE(file);
362
363     if (mpx_level_count < 1)
364     {
365       Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
366             filename);
367
368       return FALSE;
369     }
370
371     if (level_pos >= mpx_level_count)
372     {
373       Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
374             filename);
375
376       return FALSE;
377     }
378
379     mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
380
381     for (i = 0; i < mpx_level_count; i++)
382     {
383       LevelDescriptor *ldesc = &mpx_level_desc[i];
384
385       ldesc->Width  = getFile16BitLE(file);
386       ldesc->Height = getFile16BitLE(file);
387       ldesc->OffSet = getFile32BitLE(file);     /* starts with 1, not with 0 */
388       ldesc->Size   = getFile32BitLE(file);
389     }
390
391     level_width  = mpx_level_desc[level_pos].Width;
392     level_height = mpx_level_desc[level_pos].Height;
393
394     file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
395   }
396
397   /* position file stream to the requested level (in case of level package) */
398   if (fseek(file, file_seek_pos, SEEK_SET) != 0)
399   {
400     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
401
402     return FALSE;
403   }
404
405   /* there exist Supaplex level package files with multi-part levels which
406      can be detected as follows: instead of leading and trailing dashes ('-')
407      to pad the level name, they have leading and trailing numbers which are
408      the x and y coordinations of the current part of the multi-part level;
409      if there are '?' characters instead of numbers on the left or right side
410      of the level name, the multi-part level consists of only horizontal or
411      vertical parts */
412
413   for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
414   {
415     LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
416                                      demo_available);
417
418     /* check if this level is a part of a bigger multi-part level */
419
420     if (is_single_level_file)
421       break;
422
423     name_first = header->LevelTitle[0];
424     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
425
426     is_multipart_level =
427       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
428        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
429
430     is_first_part =
431       ((name_first == '?' || name_first == '1') &&
432        (name_last  == '?' || name_last  == '1'));
433
434     if (is_multipart_level)
435     {
436       /* correct leading multipart level meta information in level name */
437       for (i = 0;
438            i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
439            i++)
440         header->LevelTitle[i] = '-';
441
442       /* correct trailing multipart level meta information in level name */
443       for (i = SP_LEVEL_NAME_LEN - 1;
444            i >= 0 && header->LevelTitle[i] == name_last;
445            i--)
446         header->LevelTitle[i] = '-';
447     }
448
449     /* ---------- check for normal single level ---------- */
450
451     if (!reading_multipart_level && !is_multipart_level)
452     {
453       /* the current level is simply a normal single-part level, and we are
454          not reading a multi-part level yet, so return the level as it is */
455
456       break;
457     }
458
459     /* ---------- check for empty level (unused multi-part) ---------- */
460
461     if (!reading_multipart_level && is_multipart_level && !is_first_part)
462     {
463       /* this is a part of a multi-part level, but not the first part
464          (and we are not already reading parts of a multi-part level);
465          in this case, use an empty level instead of the single part */
466
467       use_empty_level = TRUE;
468
469       break;
470     }
471
472     /* ---------- check for finished multi-part level ---------- */
473
474     if (reading_multipart_level &&
475         (!is_multipart_level ||
476          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
477                     SP_LEVEL_NAME_LEN)))
478     {
479       /* we are already reading parts of a multi-part level, but this level is
480          either not a multi-part level, or a part of a different multi-part
481          level; in both cases, the multi-part level seems to be complete */
482
483       break;
484     }
485
486     /* ---------- here we have one part of a multi-part level ---------- */
487
488     reading_multipart_level = TRUE;
489
490     if (is_first_part)  /* start with first part of new multi-part level */
491     {
492       /* copy level info structure from first part */
493       multipart_level = native_sp_level;
494
495       /* clear playfield of new multi-part level */
496       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
497         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
498           multipart_level.playfield[x][y] = fiSpace;
499     }
500
501     if (name_first == '?')
502       name_first = '1';
503     if (name_last == '?')
504       name_last = '1';
505
506     multipart_xpos = (int)(name_first - '0');
507     multipart_ypos = (int)(name_last  - '0');
508
509 #if 0
510     printf("----------> part (%d/%d) of multi-part level '%s'\n",
511            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
512 #endif
513
514     if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
515         multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
516     {
517       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
518
519       break;
520     }
521
522     multipart_level.width  = MAX(multipart_level.width,
523                                  multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
524     multipart_level.height = MAX(multipart_level.height,
525                                  multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
526
527     /* copy level part at the right position of multi-part level */
528     for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
529     {
530       for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
531       {
532         int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
533         int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
534
535         multipart_level.playfield[start_x + x][start_y + y] =
536           native_sp_level.playfield[x][y];
537       }
538     }
539   }
540
541   fclose(file);
542
543   if (use_empty_level)
544   {
545     setLevelInfoToDefaults_SP();
546
547     Error(ERR_WARN, "single part of multi-part level -- using empty level");
548   }
549
550   if (reading_multipart_level)
551     native_sp_level = multipart_level;
552
553   copyInternalEngineVars_SP();
554
555   return TRUE;
556 }
557
558 void SaveNativeLevel_SP(char *filename)
559 {
560   LevelInfoType *header = &native_sp_level.header;
561   FILE *file;
562   int i, x, y;
563
564   if (!(file = fopen(filename, MODE_WRITE)))
565   {
566     Error(ERR_WARN, "cannot save native level file '%s'", filename);
567
568     return;
569   }
570
571   /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
572   for (y = 0; y < native_sp_level.height; y++)
573     for (x = 0; x < native_sp_level.width; x++)
574       putFile8Bit(file, native_sp_level.playfield[x][y]);
575
576   /* write level header (96 bytes) */
577
578   WriteUnusedBytesToFile(file, 4);
579
580   putFile8Bit(file, header->InitialGravity);
581   putFile8Bit(file, header->Version);
582
583   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
584     putFile8Bit(file, header->LevelTitle[i]);
585
586   putFile8Bit(file, header->InitialFreezeZonks);
587   putFile8Bit(file, header->InfotronsNeeded);
588   putFile8Bit(file, header->SpecialPortCount);
589
590   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
591   {
592     SpecialPortType *port = &header->SpecialPort[i];
593
594     putFile16BitBE(file, port->PortLocation);
595     putFile8Bit(file, port->Gravity);
596     putFile8Bit(file, port->FreezeZonks);
597     putFile8Bit(file, port->FreezeEnemies);
598
599     WriteUnusedBytesToFile(file, 1);
600   }
601
602   putFile8Bit(file, header->SpeedByte);
603   putFile8Bit(file, header->CheckSumByte);
604   putFile16BitLE(file, header->DemoRandomSeed);
605
606   /* also save demo tape, if available */
607
608   if (native_sp_level.demo.is_available)
609   {
610     putFile8Bit(file, native_sp_level.demo.level_nr);
611
612     for (i = 0; i < native_sp_level.demo.length; i++)
613       putFile8Bit(file, native_sp_level.demo.data[i]);
614   }
615
616   fclose(file);
617 }