changed comments from old to new style (multiple-line comments)
[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 static void setTapeInfoToDefaults_SP(void)
11 {
12   native_sp_level.demo.is_available = FALSE;
13   native_sp_level.demo.length = 0;
14 }
15
16 void setLevelInfoToDefaults_SP(void)
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(void)
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         break;
258     }
259
260     if (i >= SP_MAX_TAPE_LEN)
261       Error(ERR_WARN, "SP demo truncated: size exceeds maximum SP demo size %d",
262             SP_MAX_TAPE_LEN);
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     putFile8Bit(file, 0xff);    // "end of demo" byte
579   }
580
581   fclose(file);
582 }