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