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