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