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