rnd-20091101-1-src
[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, boolean demo_available)
153 {
154   LevelInfoType *header = &native_sp_level.header;
155   int i, x, y;
156
157   /* for details of the Supaplex level format, see Herman Perk's Supaplex
158      documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
159
160   native_sp_level.width  = SP_PLAYFIELD_WIDTH;
161   native_sp_level.height = SP_PLAYFIELD_HEIGHT;
162
163   /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
164   for (y = 0; y < native_sp_level.height; y++)
165     for (x = 0; x < native_sp_level.width; x++)
166       native_sp_level.playfield[x][y] = getFile8Bit(file);
167
168   /* read level header (96 bytes) */
169
170   ReadUnusedBytesFromFile(file, 4);     /* (not used by Supaplex engine) */
171
172   /* initial gravity: 1 == "on", anything else (0) == "off" */
173   header->InitialGravity = getFile8Bit(file);
174
175   /* SpeedFixVersion XOR 0x20 */
176   header->Version = getFile8Bit(file);
177
178   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
179   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
180     header->LevelTitle[i] = getFile8Bit(file);
181
182   /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
183   header->InitialFreezeZonks = getFile8Bit(file);
184
185   /* number of infotrons needed; 0 means that Supaplex will count the total
186      amount of infotrons in the level and use the low byte of that number
187      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
188   header->InfotronsNeeded = getFile8Bit(file);
189
190   /* number of special ("gravity") port entries below (maximum 10 allowed) */
191   header->SpecialPortCount = getFile8Bit(file);
192
193 #if 0
194   printf("::: num_special_ports == %d\n", header->SpecialPortCount);
195 #endif
196
197   /* database of properties of up to 10 special ports (6 bytes per port) */
198   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
199   {
200     SpecialPortType *port = &header->SpecialPort[i];
201
202     /* high and low byte of the location of a special port; if (x, y) are the
203        coordinates of a port in the field and (0, 0) is the top-left corner,
204        the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
205        of what may be expected: Supaplex works with a game field in memory
206        which is 2 bytes per tile) */
207     port->PortLocation = getFile16BitBE(file);          /* yes, big endian */
208
209 #if 0
210     {
211       int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
212       int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
213
214       printf("::: %d: port_location == %d => (%d, %d)\n",
215              i, port->PortLocation, port_x, port_y);
216     }
217 #endif
218
219     /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
220     port->Gravity = getFile8Bit(file);
221
222     /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
223     port->FreezeZonks = getFile8Bit(file);
224
225     /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
226     port->FreezeEnemies = getFile8Bit(file);
227
228     ReadUnusedBytesFromFile(file, 1);   /* (not used by Supaplex engine) */
229   }
230
231   /* SpeedByte XOR Highbyte(RandomSeed) */
232   header->SpeedByte = getFile8Bit(file);
233
234   /* CheckSum XOR SpeedByte */
235   header->CheckSumByte = getFile8Bit(file);
236
237   /* random seed used for recorded demos */
238   header->DemoRandomSeed = getFile16BitLE(file);        /* yes, little endian */
239   // header->DemoRandomSeed = getFile16BitBE(file);     /* !!! TEST ONLY !!! */
240
241 #if 0
242   printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
243 #endif
244
245   /* auto-determine number of infotrons if it was stored as "0" -- see above */
246   if (header->InfotronsNeeded == 0)
247   {
248     for (x = 0; x < native_sp_level.width; x++)
249       for (y = 0; y < native_sp_level.height; y++)
250         if (native_sp_level.playfield[x][y] == fiInfotron)
251           header->InfotronsNeeded++;
252
253     header->InfotronsNeeded &= 0xff;    /* only use low byte -- see above */
254   }
255
256   /* read raw level header bytes (96 bytes) */
257
258   fseek(file, -(SP_HEADER_SIZE), SEEK_CUR);     /* rewind file */
259   for (i = 0; i < SP_HEADER_SIZE; i++)
260     native_sp_level.header_raw_bytes[i] = fgetc(file);
261
262   /* also load demo tape, if available */
263
264   if (demo_available)
265   {
266     int level_nr = getFile8Bit(file);
267
268     level_nr &= 0x7f;                   /* clear highest bit */
269     level_nr = (level_nr < 1   ? 1   :
270                 level_nr > 111 ? 111 : level_nr);
271
272     native_sp_level.demo.level_nr = level_nr;
273
274     for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
275     {
276       native_sp_level.demo.data[i] = getFile8Bit(file);
277
278       if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
279       {
280         i++;
281
282         break;
283       }
284     }
285
286     native_sp_level.demo.length = i;
287     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
288   }
289 }
290
291 boolean LoadNativeLevel_SP(char *filename, int pos)
292 {
293   FILE *file;
294   int i, l, x, y;
295   char name_first, name_last;
296   struct LevelInfo_SP multipart_level;
297   int multipart_xpos, multipart_ypos;
298   boolean is_multipart_level;
299   boolean is_first_part;
300   boolean reading_multipart_level = FALSE;
301   boolean use_empty_level = FALSE;
302   LevelInfoType *header = &native_sp_level.header;
303   boolean demo_available = (strSuffix(filename, ".sp") ||
304                             strSuffix(filename, ".SP"));
305
306   /* always start with reliable default values */
307   setLevelInfoToDefaults_SP();
308   copyInternalEngineVars_SP();
309
310   if (!(file = fopen(filename, MODE_READ)))
311   {
312     Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
313
314     return FALSE;
315   }
316
317   /* position file stream to the requested level (in case of level package) */
318   if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
319   {
320     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
321
322     return FALSE;
323   }
324
325   /* there exist Supaplex level package files with multi-part levels which
326      can be detected as follows: instead of leading and trailing dashes ('-')
327      to pad the level name, they have leading and trailing numbers which are
328      the x and y coordinations of the current part of the multi-part level;
329      if there are '?' characters instead of numbers on the left or right side
330      of the level name, the multi-part level consists of only horizontal or
331      vertical parts */
332
333   for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
334   {
335     LoadNativeLevelFromFileStream_SP(file, demo_available);
336
337     /* check if this level is a part of a bigger multi-part level */
338
339     name_first = header->LevelTitle[0];
340     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
341
342     is_multipart_level =
343       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
344        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
345
346     is_first_part =
347       ((name_first == '?' || name_first == '1') &&
348        (name_last  == '?' || name_last  == '1'));
349
350     if (is_multipart_level)
351     {
352       /* correct leading multipart level meta information in level name */
353       for (i = 0;
354            i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
355            i++)
356         header->LevelTitle[i] = '-';
357
358       /* correct trailing multipart level meta information in level name */
359       for (i = SP_LEVEL_NAME_LEN - 1;
360            i >= 0 && header->LevelTitle[i] == name_last;
361            i--)
362         header->LevelTitle[i] = '-';
363     }
364
365     /* ---------- check for normal single level ---------- */
366
367     if (!reading_multipart_level && !is_multipart_level)
368     {
369       /* the current level is simply a normal single-part level, and we are
370          not reading a multi-part level yet, so return the level as it is */
371
372       break;
373     }
374
375     /* ---------- check for empty level (unused multi-part) ---------- */
376
377     if (!reading_multipart_level && is_multipart_level && !is_first_part)
378     {
379       /* this is a part of a multi-part level, but not the first part
380          (and we are not already reading parts of a multi-part level);
381          in this case, use an empty level instead of the single part */
382
383       use_empty_level = TRUE;
384
385       break;
386     }
387
388     /* ---------- check for finished multi-part level ---------- */
389
390     if (reading_multipart_level &&
391         (!is_multipart_level ||
392          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
393                     SP_LEVEL_NAME_LEN)))
394     {
395       /* we are already reading parts of a multi-part level, but this level is
396          either not a multi-part level, or a part of a different multi-part
397          level; in both cases, the multi-part level seems to be complete */
398
399       break;
400     }
401
402     /* ---------- here we have one part of a multi-part level ---------- */
403
404     reading_multipart_level = TRUE;
405
406     if (is_first_part)  /* start with first part of new multi-part level */
407     {
408       /* copy level info structure from first part */
409       multipart_level = native_sp_level;
410
411       /* clear playfield of new multi-part level */
412       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
413         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
414           multipart_level.playfield[x][y] = fiSpace;
415     }
416
417     if (name_first == '?')
418       name_first = '1';
419     if (name_last == '?')
420       name_last = '1';
421
422     multipart_xpos = (int)(name_first - '0');
423     multipart_ypos = (int)(name_last  - '0');
424
425 #if 0
426     printf("----------> part (%d/%d) of multi-part level '%s'\n",
427            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
428 #endif
429
430     if (multipart_xpos * SP_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
431         multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
432     {
433       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
434
435       break;
436     }
437
438     multipart_level.width  = MAX(multipart_level.width,
439                                  multipart_xpos * SP_PLAYFIELD_WIDTH);
440     multipart_level.height = MAX(multipart_level.height,
441                                  multipart_ypos * SP_PLAYFIELD_HEIGHT);
442
443     /* copy level part at the right position of multi-part level */
444     for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
445     {
446       for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
447       {
448         int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
449         int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
450
451         multipart_level.playfield[start_x + x][start_y + y] =
452           native_sp_level.playfield[x][y];
453       }
454     }
455   }
456
457   fclose(file);
458
459   if (use_empty_level)
460   {
461     setLevelInfoToDefaults_SP();
462
463     Error(ERR_WARN, "single part of multi-part level -- using empty level");
464   }
465
466   if (reading_multipart_level)
467     native_sp_level = multipart_level;
468
469   copyInternalEngineVars_SP();
470
471   return TRUE;
472 }