1
2 #include "main_sp.h"
3 #include "global.h"
4
5
6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level */
8 /* ------------------------------------------------------------------------- */
9
setTapeInfoToDefaults_SP()10 void setTapeInfoToDefaults_SP()
11 {
12 native_sp_level.demo.is_available = FALSE;
13 native_sp_level.demo.length = 0;
14 }
15
setLevelInfoToDefaults_SP()16 void setLevelInfoToDefaults_SP()
17 {
18 LevelInfoType *header = &native_sp_level.header;
19 char *empty_title = "-------- EMPTY --------";
20 int i, x, y;
21
22 native_sp_level.game_sp = &game_sp;
23
24 native_sp_level.width = SP_STD_PLAYFIELD_WIDTH;
25 native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
26
27 for (x = 0; x < native_sp_level.width; x++)
28 for (y = 0; y < native_sp_level.height; y++)
29 native_sp_level.playfield[x][y] = fiSpace;
30
31 /* copy string (without terminating '\0' character!) */
32 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
33 header->LevelTitle[i] = empty_title[i];
34
35 header->InitialGravity = 0;
36 header->Version = 0;
37 header->InitialFreezeZonks = 0;
38 header->InfotronsNeeded = 0;
39 header->SpecialPortCount = 0;
40 header->SpeedByte = 0;
41 header->CheckSumByte = 0;
42 header->DemoRandomSeed = 0;
43
44 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
45 {
46 SpecialPortType *port = &header->SpecialPort[i];
47
48 port->PortLocation = 0;
49 port->Gravity = 0;
50 port->FreezeZonks = 0;
51 port->FreezeEnemies = 0;
52 }
53
54 /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
55 for (i = 0; i < SP_HEADER_SIZE; i++)
56 native_sp_level.header_raw_bytes[i] = 0x20;
57
58 setTapeInfoToDefaults_SP();
59 }
60
copyInternalEngineVars_SP()61 void copyInternalEngineVars_SP()
62 {
63 int count;
64 int i, x, y;
65
66 LInfo = native_sp_level.header;
67
68 FieldWidth = native_sp_level.width;
69 FieldHeight = native_sp_level.height;
70 HeaderSize = 96;
71
72 FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
73 LevelMax = (FieldWidth * FieldHeight) - 1;
74
75 #if 0
76 /* (add one byte for the level number stored as first byte of demo data) */
77 FileMax = FieldMax + native_sp_level.demo.length + 1;
78 #endif
79
80 #if 0
81 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
82 DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
83 PlayField16 = REDIM_1D(sizeof(int), -game_sp.preceding_buffer_size, FieldMax);
84 #endif
85
86 /* initialize preceding playfield buffer */
87 for (i = -game_sp.preceding_buffer_size; i < 0; i++)
88 PlayField16[i] = 0;
89
90 /* initialize preceding playfield buffer */
91 for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++)
92 PlayField8[i] = 0;
93
94 count = 0;
95 for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
96 {
97 char *s = game_sp.preceding_buffer[i];
98 boolean hi_byte = FALSE; /* little endian data => start with low byte */
99
100 while (s[0] != '\0' && s[1] != '\0')
101 {
102 int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
103 int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
104 int byte = (hi_nibble << 4) | lo_nibble;
105
106 if (hi_byte)
107 byte <<= 8;
108
109 PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
110
111 if (hi_byte)
112 count++;
113
114 hi_byte = !hi_byte;
115
116 s += 2;
117
118 while (*s == ' ')
119 s++;
120 }
121 }
122
123 count = 0;
124 for (y = 0; y < native_sp_level.height; y++)
125 for (x = 0; x < native_sp_level.width; x++)
126 PlayField8[count++] = native_sp_level.playfield[x][y];
127
128 /* add raw header bytes to subsequent playfield buffer zone */
129 for (i = 0; i < SP_HEADER_SIZE; i++)
130 PlayField8[count++] = native_sp_level.header_raw_bytes[i];
131
132 for (i = 0; i < count; i++)
133 {
134 PlayField16[i] = PlayField8[i];
135 DisPlayField[i] = PlayField8[i];
136 PlayField8[i] = 0;
137 }
138
139 if (native_sp_level.demo.is_available)
140 {
141 DemoAvailable = True;
142
143 #if 0
144 /* !!! NEVER USED !!! */
145 PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
146
147 /* !!! NEVER USED !!! */
148 for (i = 0; i < native_sp_level.demo.length; i++)
149 PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
150 #endif
151 }
152
153 #if 0
154 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
155 AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
156 TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
157 #endif
158
159 GravityFlag = LInfo.InitialGravity;
160 FreezeZonks = LInfo.InitialFreezeZonks;
161
162 #if 1
163 /* this is set by main game tape code to native random generator directly */
164 #else
165 RandomSeed = LInfo.DemoRandomSeed;
166 #endif
167
168 LevelLoaded = True;
169 }
170
LoadNativeLevelFromFileStream_SP(FILE * file,int width,int height,boolean demo_available)171 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
172 boolean demo_available)
173 {
174 LevelInfoType *header = &native_sp_level.header;
175 int i, x, y;
176
177 /* for details of the Supaplex level format, see Herman Perk's Supaplex
178 documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
179
180 native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH);
181 native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
182
183 /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
184 /* (MPX levels may have non-standard playfield size -- check max. size) */
185 for (y = 0; y < height; y++)
186 {
187 for (x = 0; x < width; x++)
188 {
189 byte element = getFile8Bit(file);
190
191 if (x < SP_MAX_PLAYFIELD_WIDTH &&
192 y < SP_MAX_PLAYFIELD_HEIGHT)
193 native_sp_level.playfield[x][y] = element;
194 }
195 }
196
197 /* read level header (96 bytes) */
198
199 ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
200
201 /* initial gravity: 1 == "on", anything else (0) == "off" */
202 header->InitialGravity = getFile8Bit(file);
203
204 /* SpeedFixVersion XOR 0x20 */
205 header->Version = getFile8Bit(file);
206
207 /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
208 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
209 header->LevelTitle[i] = getFile8Bit(file);
210
211 /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
212 header->InitialFreezeZonks = getFile8Bit(file);
213
214 /* number of infotrons needed; 0 means that Supaplex will count the total
215 amount of infotrons in the level and use the low byte of that number
216 (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
217 header->InfotronsNeeded = getFile8Bit(file);
218
219 /* number of special ("gravity") port entries below (maximum 10 allowed) */
220 header->SpecialPortCount = getFile8Bit(file);
221
222 /* database of properties of up to 10 special ports (6 bytes per port) */
223 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
224 {
225 SpecialPortType *port = &header->SpecialPort[i];
226
227 /* high and low byte of the location of a special port; if (x, y) are the
228 coordinates of a port in the field and (0, 0) is the top-left corner,
229 the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
230 of what may be expected: Supaplex works with a game field in memory
231 which is 2 bytes per tile) */
232 port->PortLocation = getFile16BitBE(file); /* yes, big endian */
233
234 /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
235 port->Gravity = getFile8Bit(file);
236
237 /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
238 port->FreezeZonks = getFile8Bit(file);
239
240 /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
241 port->FreezeEnemies = getFile8Bit(file);
242
243 ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
244 }
245
246 /* SpeedByte XOR Highbyte(RandomSeed) */
247 header->SpeedByte = getFile8Bit(file);
248
249 /* CheckSum XOR SpeedByte */
250 header->CheckSumByte = getFile8Bit(file);
251
252 /* random seed used for recorded demos */
253 header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */
254
255 /* auto-determine number of infotrons if it was stored as "0" -- see above */
256 if (header->InfotronsNeeded == 0)
257 {
258 for (x = 0; x < native_sp_level.width; x++)
259 for (y = 0; y < native_sp_level.height; y++)
260 if (native_sp_level.playfield[x][y] == fiInfotron)
261 header->InfotronsNeeded++;
262
263 header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
264 }
265
266 /* read raw level header bytes (96 bytes) */
267
268 fseek(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */
269 for (i = 0; i < SP_HEADER_SIZE; i++)
270 native_sp_level.header_raw_bytes[i] = fgetc(file);
271
272 /* also load demo tape, if available (only in single level files) */
273
274 if (demo_available)
275 {
276 int level_nr = getFile8Bit(file);
277
278 level_nr &= 0x7f; /* clear highest bit */
279 level_nr = (level_nr < 1 ? 1 :
280 level_nr > 111 ? 111 : level_nr);
281
282 native_sp_level.demo.level_nr = level_nr;
283
284 for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
285 {
286 native_sp_level.demo.data[i] = getFile8Bit(file);
287
288 if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
289 {
290 i++;
291
292 break;
293 }
294 }
295
296 native_sp_level.demo.length = i;
297 native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
298 }
299 }
300
LoadNativeLevel_SP(char * filename,int level_pos,boolean level_info_only)301 boolean LoadNativeLevel_SP(char *filename, int level_pos,
302 boolean level_info_only)
303 {
304 FILE *file;
305 int i, l, x, y;
306 char name_first, name_last;
307 struct LevelInfo_SP multipart_level;
308 int multipart_xpos, multipart_ypos;
309 boolean is_multipart_level;
310 boolean is_first_part;
311 boolean reading_multipart_level = FALSE;
312 boolean use_empty_level = FALSE;
313 LevelInfoType *header = &native_sp_level.header;
314 boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
315 strSuffixLower(filename, ".mpx"));
316 boolean demo_available = is_single_level_file;
317 boolean is_mpx_file = strSuffixLower(filename, ".mpx");
318 int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
319 int level_width = SP_STD_PLAYFIELD_WIDTH;
320 int level_height = SP_STD_PLAYFIELD_HEIGHT;
321
322 /* always start with reliable default values */
323 setLevelInfoToDefaults_SP();
324 copyInternalEngineVars_SP();
325
326 if (!(file = fopen(filename, MODE_READ)))
327 {
328 if (!level_info_only)
329 Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
330
331 return FALSE;
332 }
333
334 if (is_mpx_file)
335 {
336 char mpx_chunk_name[4 + 1];
337 int mpx_version;
338 int mpx_level_count;
339 LevelDescriptor *mpx_level_desc;
340
341 getFileChunkBE(file, mpx_chunk_name, NULL);
342
343 if (!strEqual(mpx_chunk_name, "MPX "))
344 {
345 Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
346 filename);
347
348 return FALSE;
349 }
350
351 mpx_version = getFile16BitLE(file);
352
353 if (mpx_version != 1)
354 {
355 Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
356 filename);
357
358 return FALSE;
359 }
360
361 mpx_level_count = getFile16BitLE(file);
362
363 if (mpx_level_count < 1)
364 {
365 Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
366 filename);
367
368 return FALSE;
369 }
370
371 if (level_pos >= mpx_level_count)
372 {
373 Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
374 filename);
375
376 return FALSE;
377 }
378
379 mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
380
381 for (i = 0; i < mpx_level_count; i++)
382 {
383 LevelDescriptor *ldesc = &mpx_level_desc[i];
384
385 ldesc->Width = getFile16BitLE(file);
386 ldesc->Height = getFile16BitLE(file);
387 ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */
388 ldesc->Size = getFile32BitLE(file);
389 }
390
391 level_width = mpx_level_desc[level_pos].Width;
392 level_height = mpx_level_desc[level_pos].Height;
393
394 file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
395 }
396
397 /* position file stream to the requested level (in case of level package) */
398 if (fseek(file, file_seek_pos, SEEK_SET) != 0)
399 {
400 Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
401
402 return FALSE;
403 }
404
405 /* there exist Supaplex level package files with multi-part levels which
406 can be detected as follows: instead of leading and trailing dashes ('-')
407 to pad the level name, they have leading and trailing numbers which are
408 the x and y coordinations of the current part of the multi-part level;
409 if there are '?' characters instead of numbers on the left or right side
410 of the level name, the multi-part level consists of only horizontal or
411 vertical parts */
412
413 for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
414 {
415 LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
416 demo_available);
417
418 /* check if this level is a part of a bigger multi-part level */
419
420 if (is_single_level_file)
421 break;
422
423 name_first = header->LevelTitle[0];
424 name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
425
426 is_multipart_level =
427 ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
428 (name_last == '?' || (name_last >= '0' && name_last <= '9')));
429
430 is_first_part =
431 ((name_first == '?' || name_first == '1') &&
432 (name_last == '?' || name_last == '1'));
433
434 if (is_multipart_level)
435 {
436 /* correct leading multipart level meta information in level name */
437 for (i = 0;
438 i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
439 i++)
440 header->LevelTitle[i] = '-';
441
442 /* correct trailing multipart level meta information in level name */
443 for (i = SP_LEVEL_NAME_LEN - 1;
444 i >= 0 && header->LevelTitle[i] == name_last;
445 i--)
446 header->LevelTitle[i] = '-';
447 }
448
449 /* ---------- check for normal single level ---------- */
450
451 if (!reading_multipart_level && !is_multipart_level)
452 {
453 /* the current level is simply a normal single-part level, and we are
454 not reading a multi-part level yet, so return the level as it is */
455
456 break;
457 }
458
459 /* ---------- check for empty level (unused multi-part) ---------- */
460
461 if (!reading_multipart_level && is_multipart_level && !is_first_part)
462 {
463 /* this is a part of a multi-part level, but not the first part
464 (and we are not already reading parts of a multi-part level);
465 in this case, use an empty level instead of the single part */
466
467 use_empty_level = TRUE;
468
469 break;
470 }
471
472 /* ---------- check for finished multi-part level ---------- */
473
474 if (reading_multipart_level &&
475 (!is_multipart_level ||
476 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
477 SP_LEVEL_NAME_LEN)))
478 {
479 /* we are already reading parts of a multi-part level, but this level is
480 either not a multi-part level, or a part of a different multi-part
481 level; in both cases, the multi-part level seems to be complete */
482
483 break;
484 }
485
486 /* ---------- here we have one part of a multi-part level ---------- */
487
488 reading_multipart_level = TRUE;
489
490 if (is_first_part) /* start with first part of new multi-part level */
491 {
492 /* copy level info structure from first part */
493 multipart_level = native_sp_level;
494
495 /* clear playfield of new multi-part level */
496 for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
497 for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
498 multipart_level.playfield[x][y] = fiSpace;
499 }
500
501 if (name_first == '?')
502 name_first = '1';
503 if (name_last == '?')
504 name_last = '1';
505
506 multipart_xpos = (int)(name_first - '0');
507 multipart_ypos = (int)(name_last - '0');
508
509 #if 0
510 printf("----------> part (%d/%d) of multi-part level '%s'\n",
511 multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
512 #endif
513
514 if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
515 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
516 {
517 Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
518
519 break;
520 }
521
522 multipart_level.width = MAX(multipart_level.width,
523 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
524 multipart_level.height = MAX(multipart_level.height,
525 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
526
527 /* copy level part at the right position of multi-part level */
528 for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
529 {
530 for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
531 {
532 int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
533 int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
534
535 multipart_level.playfield[start_x + x][start_y + y] =
536 native_sp_level.playfield[x][y];
537 }
538 }
539 }
540
541 fclose(file);
542
543 if (use_empty_level)
544 {
545 setLevelInfoToDefaults_SP();
546
547 Error(ERR_WARN, "single part of multi-part level -- using empty level");
548 }
549
550 if (reading_multipart_level)
551 native_sp_level = multipart_level;
552
553 copyInternalEngineVars_SP();
554
555 return TRUE;
556 }
557
SaveNativeLevel_SP(char * filename)558 void SaveNativeLevel_SP(char *filename)
559 {
560 LevelInfoType *header = &native_sp_level.header;
561 FILE *file;
562 int i, x, y;
563
564 if (!(file = fopen(filename, MODE_WRITE)))
565 {
566 Error(ERR_WARN, "cannot save native level file '%s'", filename);
567
568 return;
569 }
570
571 /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
572 for (y = 0; y < native_sp_level.height; y++)
573 for (x = 0; x < native_sp_level.width; x++)
574 putFile8Bit(file, native_sp_level.playfield[x][y]);
575
576 /* write level header (96 bytes) */
577
578 WriteUnusedBytesToFile(file, 4);
579
580 putFile8Bit(file, header->InitialGravity);
581 putFile8Bit(file, header->Version);
582
583 for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
584 putFile8Bit(file, header->LevelTitle[i]);
585
586 putFile8Bit(file, header->InitialFreezeZonks);
587 putFile8Bit(file, header->InfotronsNeeded);
588 putFile8Bit(file, header->SpecialPortCount);
589
590 for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
591 {
592 SpecialPortType *port = &header->SpecialPort[i];
593
594 putFile16BitBE(file, port->PortLocation);
595 putFile8Bit(file, port->Gravity);
596 putFile8Bit(file, port->FreezeZonks);
597 putFile8Bit(file, port->FreezeEnemies);
598
599 WriteUnusedBytesToFile(file, 1);
600 }
601
602 putFile8Bit(file, header->SpeedByte);
603 putFile8Bit(file, header->CheckSumByte);
604 putFile16BitLE(file, header->DemoRandomSeed);
605
606 /* also save demo tape, if available */
607
608 if (native_sp_level.demo.is_available)
609 {
610 putFile8Bit(file, native_sp_level.demo.level_nr);
611
612 for (i = 0; i < native_sp_level.demo.length; i++)
613 putFile8Bit(file, native_sp_level.demo.data[i]);
614 }
615
616 fclose(file);
617 }
618