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