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