fixed element scores for MM style elements (MM engine)
[rocksndiamonds.git] / src / game_mm / mm_files.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_files.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <ctype.h>
15 #include <dirent.h>
16
17 #include "main_mm.h"
18
19 #include "mm_main.h"
20
21 #define CHUNK_ID_LEN            4       /* IFF style chunk id length */
22 #define CHUNK_SIZE_UNDEFINED    0       /* undefined chunk size == 0  */
23 #define CHUNK_SIZE_NONE         -1      /* do not write chunk size    */
24 #define FILE_VERS_CHUNK_SIZE    8       /* size of file version chunk */
25 #define LEVEL_HEADER_SIZE       80      /* size of level file header */
26 #define LEVEL_HEADER_UNUSED     19      /* unused level header bytes */
27
28 /* file identifier strings */
29 #define LEVEL_COOKIE_TMPL       "MIRRORMAGIC_LEVEL_FILE_VERSION_x.x"
30 #define SCORE_COOKIE            "MIRRORMAGIC_SCORE_FILE_VERSION_1.4"
31
32
33 int default_score[LEVEL_SCORE_ELEMENTS] =
34 {
35   [SC_COLLECTIBLE]      = 10,
36   [SC_PACMAN]           = 50,
37   [SC_KEY]              = 10,
38   [SC_TIME_BONUS]       = 1,
39   [SC_LIGHTBALL]        = 10,
40 };
41
42
43 /* ========================================================================= */
44 /* level file functions                                                      */
45 /* ========================================================================= */
46
47 static void ReadChunk_MM_VERS(File *file, int *file_version, int *game_version)
48 {
49   int file_version_major, file_version_minor, file_version_patch;
50   int game_version_major, game_version_minor, game_version_patch;
51
52   file_version_major = getFile8Bit(file);
53   file_version_minor = getFile8Bit(file);
54   file_version_patch = getFile8Bit(file);
55   getFile8Bit(file);            /* not used */
56
57   game_version_major = getFile8Bit(file);
58   game_version_minor = getFile8Bit(file);
59   game_version_patch = getFile8Bit(file);
60   getFile8Bit(file);            /* not used */
61
62   *file_version = MM_VERSION_IDENT(file_version_major,
63                                    file_version_minor,
64                                    file_version_patch);
65
66   *game_version = MM_VERSION_IDENT(game_version_major,
67                                    game_version_minor,
68                                    game_version_patch);
69 }
70
71 static void WriteChunk_MM_VERS(FILE *file, int file_version, int game_version)
72 {
73   int file_version_major = MM_VERSION_MAJOR(file_version);
74   int file_version_minor = MM_VERSION_MINOR(file_version);
75   int file_version_patch = MM_VERSION_PATCH(file_version);
76   int game_version_major = MM_VERSION_MAJOR(game_version);
77   int game_version_minor = MM_VERSION_MINOR(game_version);
78   int game_version_patch = MM_VERSION_PATCH(game_version);
79
80   fputc(file_version_major, file);
81   fputc(file_version_minor, file);
82   fputc(file_version_patch, file);
83   fputc(0, file);       /* not used */
84
85   fputc(game_version_major, file);
86   fputc(game_version_minor, file);
87   fputc(game_version_patch, file);
88   fputc(0, file);       /* not used */
89 }
90
91 void setLevelInfoToDefaults_MM()
92 {
93   int i, x, y;
94
95   native_mm_level.file_version = MM_FILE_VERSION_ACTUAL;
96   native_mm_level.game_version = MM_GAME_VERSION_ACTUAL;
97
98   native_mm_level.encoding_16bit_field = FALSE; /* default: only 8-bit elements */
99
100   lev_fieldx = native_mm_level.fieldx = STD_LEV_FIELDX;
101   lev_fieldy = native_mm_level.fieldy = STD_LEV_FIELDY;
102
103   for (x = 0; x < MAX_LEV_FIELDX; x++)
104     for (y = 0; y < MAX_LEV_FIELDY; y++)
105       native_mm_level.field[x][y] = Feld[x][y] = Ur[x][y] = EL_EMPTY;
106
107   native_mm_level.time = 100;
108   native_mm_level.kettles_needed = 0;
109   native_mm_level.auto_count_kettles = TRUE;
110   native_mm_level.amoeba_speed = 0;
111   native_mm_level.time_fuse = 0;
112   native_mm_level.laser_red = FALSE;
113   native_mm_level.laser_green = FALSE;
114   native_mm_level.laser_blue = TRUE;
115
116   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
117     native_mm_level.name[i] = '\0';
118   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
119     native_mm_level.author[i] = '\0';
120
121   strcpy(native_mm_level.name, NAMELESS_LEVEL_NAME);
122   strcpy(native_mm_level.author, ANONYMOUS_NAME);
123
124   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
125     native_mm_level.score[i] = 10;
126
127   native_mm_level.field[0][0] = Feld[0][0] = Ur[0][0] = EL_MCDUFFIN_RIGHT;
128   native_mm_level.field[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
129     Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
130     Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED;
131 }
132
133 static int checkLevelElement(int element)
134 {
135   if (element >= EL_FIRST_RUNTIME_EL)
136   {
137     Error(ERR_WARN, "invalid level element %d", element);
138     element = EL_CHAR_FRAGE;
139   }
140
141   return element;
142 }
143
144 static int LoadLevel_MM_VERS(File *file, int chunk_size,
145                              struct LevelInfo_MM *level)
146 {
147   ReadChunk_MM_VERS(file, &level->file_version, &level->game_version);
148
149   return chunk_size;
150 }
151
152 static int LoadLevel_MM_HEAD(File *file, int chunk_size,
153                              struct LevelInfo_MM *level)
154 {
155   int i;
156   int laser_color;
157
158   lev_fieldx = level->fieldx = getFile8Bit(file);
159   lev_fieldy = level->fieldy = getFile8Bit(file);
160
161   level->time           = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
162   level->kettles_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
163
164   // one time unit was equivalent to four seconds in level files up to 2.0.x
165   if (level->file_version <= MM_FILE_VERSION_2_0)
166     level->time *= 4;
167
168   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
169     level->name[i] = getFile8Bit(file);
170   level->name[MAX_LEVEL_NAME_LEN] = 0;
171
172   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
173     level->score[i] = getFile8Bit(file);
174
175   // scores were 0 and hardcoded in game engine in level files up to 2.0.x
176   if (level->file_version <= MM_FILE_VERSION_2_0)
177     for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
178       if (level->score[i] == 0)
179         level->score[i] = default_score[i];
180
181   level->auto_count_kettles     = (getFile8Bit(file) == 1 ? TRUE : FALSE);
182   level->amoeba_speed           = getFile8Bit(file);
183   level->time_fuse              = getFile8Bit(file);
184
185   laser_color                   = getFile8Bit(file);
186   level->laser_red              = (laser_color >> 2) & 0x01;
187   level->laser_green            = (laser_color >> 1) & 0x01;
188   level->laser_blue             = (laser_color >> 0) & 0x01;
189
190   level->encoding_16bit_field   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
191
192   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
193
194   return chunk_size;
195 }
196
197 static int LoadLevel_MM_AUTH(File *file, int chunk_size,
198                              struct LevelInfo_MM *level)
199 {
200   int i;
201
202   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
203     level->author[i] = getFile8Bit(file);
204   level->author[MAX_LEVEL_NAME_LEN] = 0;
205
206   return chunk_size;
207 }
208
209 static int LoadLevel_MM_BODY(File *file, int chunk_size,
210                              struct LevelInfo_MM *level)
211 {
212   int x, y;
213   int chunk_size_expected = level->fieldx * level->fieldy;
214
215   /* Note: "chunk_size" was wrong before version 2.0 when elements are
216      stored with 16-bit encoding (and should be twice as big then).
217      Even worse, playfield data was stored 16-bit when only yamyam content
218      contained 16-bit elements and vice versa. */
219
220   if (level->encoding_16bit_field && level->file_version >= MM_FILE_VERSION_2_0)
221     chunk_size_expected *= 2;
222
223   if (chunk_size_expected != chunk_size)
224   {
225     ReadUnusedBytesFromFile(file, chunk_size);
226
227     return chunk_size_expected;
228   }
229
230   for (y = 0; y < level->fieldy; y++)
231     for (x = 0; x < level->fieldx; x++)
232       native_mm_level.field[x][y] = Feld[x][y] = Ur[x][y] =
233         checkLevelElement(level->encoding_16bit_field ?
234                           getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) :
235                           getFile8Bit(file));
236   return chunk_size;
237 }
238
239 boolean LoadNativeLevel_MM(char *filename, boolean level_info_only)
240 {
241   char cookie[MAX_LINE_LEN];
242   char chunk_name[CHUNK_ID_LEN + 1];
243   int chunk_size;
244   File *file;
245
246   static struct
247   {
248     char *name;
249     int size;
250     int (*loader)(File *, int, struct LevelInfo_MM *);
251   }
252   chunk_info[] =
253   {
254     { "VERS", FILE_VERS_CHUNK_SIZE,     LoadLevel_MM_VERS },
255     { "HEAD", LEVEL_HEADER_SIZE,        LoadLevel_MM_HEAD },
256     { "AUTH", MAX_LEVEL_AUTHOR_LEN,     LoadLevel_MM_AUTH },
257     { "BODY", -1,                       LoadLevel_MM_BODY },
258     {  NULL,  0,                        NULL }
259   };
260
261   /* always start with reliable default values */
262   setLevelInfoToDefaults_MM();
263
264   if (!(file = openFile(filename, MODE_READ)))
265   {
266     if (!level_info_only)
267       Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
268
269     return FALSE;
270   }
271
272   getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
273   if (strcmp(chunk_name, "MMII") == 0)
274   {
275     getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);   /* not used */
276
277     getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
278     if (strcmp(chunk_name, "CAVE") != 0)
279     {
280       Error(ERR_WARN, "unknown format of level file '%s'", filename);
281
282       closeFile(file);
283
284       return FALSE;
285     }
286   }
287   else  /* check for pre-2.0 file format with cookie string */
288   {
289     strcpy(cookie, chunk_name);
290     getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4);
291     if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
292       cookie[strlen(cookie) - 1] = '\0';
293
294     if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
295     {
296       Error(ERR_WARN, "unknown format of level file '%s'", filename);
297
298       closeFile(file);
299
300       return FALSE;
301     }
302
303     if ((native_mm_level.file_version = getFileVersionFromCookieString(cookie))
304         == -1)
305     {
306       Error(ERR_WARN, "unsupported version of level file '%s'", filename);
307
308       closeFile(file);
309
310       return FALSE;
311     }
312   }
313
314   while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN))
315   {
316     int i = 0;
317
318     while (chunk_info[i].name != NULL &&
319            strcmp(chunk_name, chunk_info[i].name) != 0)
320       i++;
321
322     if (chunk_info[i].name == NULL)
323     {
324       Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
325             chunk_name, filename);
326
327       ReadUnusedBytesFromFile(file, chunk_size);
328     }
329     else if (chunk_info[i].size != -1 &&
330              chunk_info[i].size != chunk_size)
331     {
332       Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
333             chunk_size, chunk_name, filename);
334
335       ReadUnusedBytesFromFile(file, chunk_size);
336     }
337     else
338     {
339       /* call function to load this level chunk */
340       int chunk_size_expected =
341         (chunk_info[i].loader)(file, chunk_size, &native_mm_level);
342
343       /* the size of some chunks cannot be checked before reading other
344          chunks first (like "HEAD" and "BODY") that contain some header
345          information, so check them here */
346       if (chunk_size_expected != chunk_size)
347         Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
348               chunk_size, chunk_name, filename);
349     }
350   }
351
352   closeFile(file);
353
354   return TRUE;
355 }
356
357 static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level)
358 {
359   int i;
360   int laser_color;
361
362   fputc(level->fieldx, file);
363   fputc(level->fieldy, file);
364
365   putFile16BitInteger(file, level->time,           BYTE_ORDER_BIG_ENDIAN);
366   putFile16BitInteger(file, level->kettles_needed, BYTE_ORDER_BIG_ENDIAN);
367
368   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
369     fputc(level->name[i], file);
370
371   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
372     fputc(level->score[i], file);
373
374   fputc((level->auto_count_kettles ? 1 : 0), file);
375   fputc(level->amoeba_speed, file);
376   fputc(level->time_fuse, file);
377
378   laser_color = ((level->laser_red   << 2) |
379                  (level->laser_green << 1) |
380                  (level->laser_blue  << 0));
381   fputc(laser_color, file);
382
383   fputc((level->encoding_16bit_field ? 1 : 0), file);
384
385   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
386 }
387
388 static void SaveLevel_MM_AUTH(FILE *file, struct LevelInfo_MM *level)
389 {
390   int i;
391
392   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
393     fputc(level->author[i], file);
394 }
395
396 static void SaveLevel_MM_BODY(FILE *file, struct LevelInfo_MM *level)
397 {
398   int x, y;
399
400   for (y = 0; y < level->fieldy; y++)
401     for (x = 0; x < level->fieldx; x++)
402       if (level->encoding_16bit_field)
403         putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN);
404       else
405         fputc(Ur[x][y], file);
406 }
407
408 void SaveNativeLevel_MM(char *filename)
409 {
410   int x, y;
411   int body_chunk_size;
412   FILE *file;
413
414   if (!(file = fopen(filename, MODE_WRITE)))
415   {
416     Error(ERR_WARN, "cannot save level file '%s'", filename);
417
418     return;
419   }
420
421   /* check level field for 16-bit elements */
422   native_mm_level.encoding_16bit_field = FALSE;
423
424   for (y = 0; y < native_mm_level.fieldy; y++)
425     for (x = 0; x < native_mm_level.fieldx; x++)
426       if (Ur[x][y] > 255)
427         native_mm_level.encoding_16bit_field = TRUE;
428
429   body_chunk_size =
430     native_mm_level.fieldx * native_mm_level.fieldy *
431     (native_mm_level.encoding_16bit_field ? 2 : 1);
432
433   putFileChunk(file, "MMII", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN);
434   putFileChunk(file, "CAVE", CHUNK_SIZE_NONE,      BYTE_ORDER_BIG_ENDIAN);
435
436   putFileChunk(file, "VERS", FILE_VERS_CHUNK_SIZE, BYTE_ORDER_BIG_ENDIAN);
437   WriteChunk_MM_VERS(file, MM_FILE_VERSION_ACTUAL, MM_GAME_VERSION_ACTUAL);
438
439   putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
440   SaveLevel_MM_HEAD(file, &native_mm_level);
441
442   putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
443   SaveLevel_MM_AUTH(file, &native_mm_level);
444
445   putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN);
446   SaveLevel_MM_BODY(file, &native_mm_level);
447
448   fclose(file);
449
450   SetFilePermissions(filename, PERMS_PRIVATE);
451 }