rnd-20091028-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   /* set by main game tape code 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
234 #if 0
235   printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
236 #endif
237
238   /* auto-determine number of infotrons if it was stored as "0" -- see above */
239   if (header->InfotronsNeeded == 0)
240   {
241     for (x = 0; x < native_sp_level.width; x++)
242       for (y = 0; y < native_sp_level.height; y++)
243         if (native_sp_level.playfield[x][y] == fiInfotron)
244           header->InfotronsNeeded++;
245
246     header->InfotronsNeeded &= 0xff;    /* only use low byte -- see above */
247   }
248
249   /* read raw level header bytes (96 bytes) */
250
251   fseek(file, -(SP_HEADER_SIZE), SEEK_CUR);     /* rewind file */
252   for (i = 0; i < SP_HEADER_SIZE; i++)
253     native_sp_level.header_raw_bytes[i] = fgetc(file);
254
255   /* also load demo tape, if available */
256
257   if (demo_available)
258   {
259     int level_nr = getFile8Bit(file);
260
261     level_nr &= 0x7f;                   /* clear highest bit */
262     level_nr = (level_nr < 1   ? 1   :
263                 level_nr > 111 ? 111 : level_nr);
264
265     native_sp_level.demo.level_nr = level_nr;
266
267     for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
268     {
269       native_sp_level.demo.data[i] = getFile8Bit(file);
270
271       if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
272       {
273         i++;
274
275         break;
276       }
277     }
278
279     native_sp_level.demo.length = i;
280     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
281   }
282 }
283
284 boolean LoadNativeLevel_SP(char *filename, int pos)
285 {
286   FILE *file;
287   int i, l, x, y;
288   char name_first, name_last;
289   struct LevelInfo_SP multipart_level;
290   int multipart_xpos, multipart_ypos;
291   boolean is_multipart_level;
292   boolean is_first_part;
293   boolean reading_multipart_level = FALSE;
294   boolean use_empty_level = FALSE;
295   LevelInfoType *header = &native_sp_level.header;
296   boolean demo_available = (strSuffix(filename, ".sp") ||
297                             strSuffix(filename, ".SP"));
298
299   /* always start with reliable default values */
300   setLevelInfoToDefaults_SP();
301   copyInternalEngineVars_SP();
302
303   if (!(file = fopen(filename, MODE_READ)))
304   {
305     Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
306
307     return FALSE;
308   }
309
310   /* position file stream to the requested level (in case of level package) */
311   if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
312   {
313     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
314
315     return FALSE;
316   }
317
318   /* there exist Supaplex level package files with multi-part levels which
319      can be detected as follows: instead of leading and trailing dashes ('-')
320      to pad the level name, they have leading and trailing numbers which are
321      the x and y coordinations of the current part of the multi-part level;
322      if there are '?' characters instead of numbers on the left or right side
323      of the level name, the multi-part level consists of only horizontal or
324      vertical parts */
325
326   for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
327   {
328     LoadNativeLevelFromFileStream_SP(file, demo_available);
329
330     /* check if this level is a part of a bigger multi-part level */
331
332     name_first = header->LevelTitle[0];
333     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
334
335     is_multipart_level =
336       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
337        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
338
339     is_first_part =
340       ((name_first == '?' || name_first == '1') &&
341        (name_last  == '?' || name_last  == '1'));
342
343     /* correct leading multipart level meta information in level name */
344     for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
345       header->LevelTitle[i] = '-';
346
347     /* correct trailing multipart level meta information in level name */
348     for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
349       header->LevelTitle[i] = '-';
350
351     /* ---------- check for normal single level ---------- */
352
353     if (!reading_multipart_level && !is_multipart_level)
354     {
355       /* the current level is simply a normal single-part level, and we are
356          not reading a multi-part level yet, so return the level as it is */
357
358       break;
359     }
360
361     /* ---------- check for empty level (unused multi-part) ---------- */
362
363     if (!reading_multipart_level && is_multipart_level && !is_first_part)
364     {
365       /* this is a part of a multi-part level, but not the first part
366          (and we are not already reading parts of a multi-part level);
367          in this case, use an empty level instead of the single part */
368
369       use_empty_level = TRUE;
370
371       break;
372     }
373
374     /* ---------- check for finished multi-part level ---------- */
375
376     if (reading_multipart_level &&
377         (!is_multipart_level ||
378          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
379                     SP_LEVEL_NAME_LEN)))
380     {
381       /* we are already reading parts of a multi-part level, but this level is
382          either not a multi-part level, or a part of a different multi-part
383          level; in both cases, the multi-part level seems to be complete */
384
385       break;
386     }
387
388     /* ---------- here we have one part of a multi-part level ---------- */
389
390     reading_multipart_level = TRUE;
391
392     if (is_first_part)  /* start with first part of new multi-part level */
393     {
394       /* copy level info structure from first part */
395       multipart_level = native_sp_level;
396
397       /* clear playfield of new multi-part level */
398       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
399         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
400           multipart_level.playfield[x][y] = fiSpace;
401     }
402
403     if (name_first == '?')
404       name_first = '1';
405     if (name_last == '?')
406       name_last = '1';
407
408     multipart_xpos = (int)(name_first - '0');
409     multipart_ypos = (int)(name_last  - '0');
410
411 #if 0
412     printf("----------> part (%d/%d) of multi-part level '%s'\n",
413            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
414 #endif
415
416     if (multipart_xpos * SP_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
417         multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
418     {
419       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
420
421       break;
422     }
423
424     multipart_level.width  = MAX(multipart_level.width,
425                                  multipart_xpos * SP_PLAYFIELD_WIDTH);
426     multipart_level.height = MAX(multipart_level.height,
427                                  multipart_ypos * SP_PLAYFIELD_HEIGHT);
428
429     /* copy level part at the right position of multi-part level */
430     for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
431     {
432       for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
433       {
434         int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
435         int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
436
437         multipart_level.playfield[start_x + x][start_y + y] =
438           native_sp_level.playfield[x][y];
439       }
440     }
441   }
442
443   fclose(file);
444
445   if (use_empty_level)
446   {
447     setLevelInfoToDefaults_SP();
448
449     Error(ERR_WARN, "single part of multi-part level -- using empty level");
450   }
451
452   if (reading_multipart_level)
453     native_sp_level = multipart_level;
454
455   copyInternalEngineVars_SP();
456
457   return TRUE;
458 }