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