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