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