#define CHUNK_SIZE_NONE -1 /* do not write chunk size */
#define FILE_VERS_CHUNK_SIZE 8 /* size of file version chunk */
#define LEVEL_HEADER_SIZE 80 /* size of level file header */
-#define LEVEL_HEADER_UNUSED 2 /* unused level header bytes */
+#define LEVEL_HEADER_UNUSED 1 /* unused level header bytes */
#define LEVEL_CHUNK_CNT2_SIZE 160 /* size of level CNT2 chunk */
#define LEVEL_CHUNK_CNT2_UNUSED 11 /* unused CNT2 chunk bytes */
#define LEVEL_CHUNK_CNT3_HEADER 16 /* size of level CNT3 header */
level->double_speed = FALSE;
level->initial_gravity = FALSE;
level->em_slippery_gems = FALSE;
- level->block_last_field = FALSE;
- level->sp_block_last_field = TRUE;
level->instant_relocation = FALSE;
level->can_pass_to_walkable = FALSE;
+ level->grow_into_diggable = TRUE;
+
+ level->block_last_field = FALSE; /* EM does not block by default */
+ level->sp_block_last_field = TRUE; /* SP blocks the last field */
+ level->block_delay = 8; /* when blocking, block 8 frames */
+ level->sp_block_delay = 9; /* SP indeed blocks 9 frames, not 8 */
level->can_move_into_acid_bits = ~0; /* everything can move into acid */
- level->dont_collide_with_bits = ~0; /* always deadly when colliding */
+ level->dont_collide_with_bits = ~0; /* always deadly when colliding */
level->use_spring_bug = FALSE;
level->use_step_counter = FALSE;
element_info[element].use_gfx_element = FALSE;
element_info[element].gfx_element = EL_EMPTY_SPACE;
+
+ element_info[element].modified_settings = FALSE;
}
if (IS_CUSTOM_ELEMENT(element) ||
element_info[element].move_pattern = MV_ALL_DIRECTIONS;
element_info[element].move_direction_initial = MV_START_AUTOMATIC;
element_info[element].move_stepsize = TILEX / 8;
+
element_info[element].move_enter_element = EL_EMPTY_SPACE;
element_info[element].move_leave_element = EL_EMPTY_SPACE;
element_info[element].move_leave_type = LEAVE_TYPE_UNLIMITED;
element_info[element].slippery_type = SLIPPERY_ANY_RANDOM;
+ element_info[element].explosion_type = EXPLODES_3X3;
element_info[element].explosion_delay = 16;
element_info[element].ignition_delay = 8;
element_info[element].walk_to_action = 0;
element_info[element].smash_targets = 0;
element_info[element].deadliness = 0;
- element_info[element].consistency = 0;
element_info[element].can_explode_by_fire = FALSE;
element_info[element].can_explode_smashed = FALSE;
/* now set default properties */
SET_PROPERTY(element, EP_CAN_MOVE_INTO_ACID, TRUE);
-
- element_info[element].modified_settings = FALSE;
}
if (IS_GROUP_ELEMENT(element) ||
level->no_valid_file = FALSE;
+ level->changed = FALSE;
+
if (leveldir_current == NULL) /* only when dumping level */
return;
level->instant_relocation = (getFile8Bit(file) == 1 ? TRUE : FALSE);
level->can_pass_to_walkable = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+ level->grow_into_diggable = (getFile8Bit(file) == 1 ? TRUE : FALSE);
ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
ei->explosion_delay = getFile8Bit(file);
ei->ignition_delay = getFile8Bit(file);
+ ei->explosion_type = getFile8Bit(file);
/* some free bytes for future custom property values and padding */
- ReadUnusedBytesFromFile(file, 2);
+ ReadUnusedBytesFromFile(file, 1);
/* read change property values */
static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
int nr)
{
+ int num_special_ports;
int i, x, y;
+ /* for details of the Supaplex level format, see Herman Perk's Supaplex
+ documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
+
/* read level body (width * height == 60 * 24 tiles == 1440 bytes) */
for (y = 0; y < SP_LEVEL_YSIZE; y++)
{
}
}
- ReadUnusedBytesFromFile(file, 4);
+ ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
- /* Initial gravitation: 1 == "on", anything else (0) == "off" */
+ /* initial gravity: 1 == "on", anything else (0) == "off" */
level->initial_gravity = (fgetc(file) == 1 ? TRUE : FALSE);
- ReadUnusedBytesFromFile(file, 1);
+ ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
/* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
level->name[i] = fgetc(file);
level->name[SP_LEVEL_NAME_LEN] = '\0';
- /* initial "freeze zonks": 2 == "on", anything else (0) == "off" */
- ReadUnusedBytesFromFile(file, 1); /* !!! NOT SUPPORTED YET !!! */
+ /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
+ ReadUnusedBytesFromFile(file, 1); /* (not used by R'n'D engine) */
/* number of infotrons needed; 0 means that Supaplex will count the total
- amount of infotrons in the level and use the low byte of that number.
+ amount of infotrons in the level and use the low byte of that number
(a multiple of 256 infotrons will result in "0 infotrons needed"!) */
level->gems_needed = fgetc(file);
- /* information about special gravity port entries */
- ReadUnusedBytesFromFile(file, 65); /* !!! NOT SUPPORTED YET !!! */
+ /* number of special ("gravity") port entries below (maximum 10 allowed) */
+ num_special_ports = fgetc(file);
+
+ /* database of properties of up to 10 special ports (6 bytes per port) */
+ for (i = 0; i < 10; i++)
+ {
+ int port_location, port_x, port_y, port_element;
+ int gravity;
+
+ /* high and low byte of the location of a special port; if (x, y) are the
+ coordinates of a port in the field and (0, 0) is the top-left corner,
+ the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
+ of what may be expected: Supaplex works with a game field in memory
+ which is 2 bytes per tile) */
+ port_location = getFile16BitBE(file);
+
+ /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
+ gravity = fgetc(file);
+
+ /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
+ ReadUnusedBytesFromFile(file, 1); /* (not used by R'n'D engine) */
+
+ /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
+ ReadUnusedBytesFromFile(file, 1); /* (not used by R'n'D engine) */
+
+ ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
+
+ if (i >= num_special_ports)
+ continue;
+
+ port_x = (port_location / 2) % SP_LEVEL_XSIZE;
+ port_y = (port_location / 2) / SP_LEVEL_XSIZE;
+
+ if (port_x < 0 || port_x >= SP_LEVEL_XSIZE ||
+ port_y < 0 || port_y >= SP_LEVEL_YSIZE)
+ {
+ Error(ERR_WARN, "special port position (%d, %d) out of bounds",
+ port_x, port_y);
+
+ continue;
+ }
+
+ port_element = level->field[port_x][port_y];
+
+ if (port_element < EL_SP_GRAVITY_PORT_RIGHT ||
+ port_element > EL_SP_GRAVITY_PORT_UP)
+ {
+ Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y);
+
+ continue;
+ }
+
+ /* change previous (wrong) gravity inverting special port to either
+ gravity enabling special port or gravity disabling special port */
+ level->field[port_x][port_y] +=
+ (gravity == 1 ? EL_SP_GRAVITY_ON_PORT_RIGHT :
+ EL_SP_GRAVITY_OFF_PORT_RIGHT) - EL_SP_GRAVITY_PORT_RIGHT;
+ }
+
+ ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
+
+ /* change special gravity ports without database entries to normal ports */
+ for (y = 0; y < SP_LEVEL_YSIZE; y++)
+ for (x = 0; x < SP_LEVEL_XSIZE; x++)
+ if (level->field[x][y] >= EL_SP_GRAVITY_PORT_RIGHT &&
+ level->field[x][y] <= EL_SP_GRAVITY_PORT_UP)
+ level->field[x][y] += EL_SP_PORT_RIGHT - EL_SP_GRAVITY_PORT_RIGHT;
+
+ /* auto-determine number of infotrons if it was stored as "0" -- see above */
+ if (level->gems_needed == 0)
+ {
+ for (y = 0; y < SP_LEVEL_YSIZE; y++)
+ for (x = 0; x < SP_LEVEL_XSIZE; x++)
+ if (level->field[x][y] == EL_SP_INFOTRON)
+ level->gems_needed++;
+
+ level->gems_needed &= 0xff; /* only use low byte -- see above */
+ }
level->fieldx = SP_LEVEL_XSIZE;
level->fieldy = SP_LEVEL_YSIZE;
pre-2.0 levels, where the game version is still taken from the
file format version used to store the level -- see above). */
+#if 1
+ /* player was faster than enemies in 1.0.0 and before */
+ if (level->file_version == FILE_VERSION_1_0)
+ level->double_speed = TRUE;
+#else
/* do some special adjustments to support older level versions */
if (level->file_version == FILE_VERSION_1_0)
{
/* player was faster than monsters in (pre-)1.0 levels */
level->double_speed = TRUE;
}
+#endif
- /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */
+ /* default behaviour for EM style gems was "slippery" only in 2.0.1 */
if (level->game_version == VERSION_IDENT(2,0,1,0))
level->em_slippery_gems = TRUE;
+ /* springs could be pushed over pits before (pre-release version) 2.2.0 */
if (level->game_version < VERSION_IDENT(2,2,0,0))
level->use_spring_bug = TRUE;
+ /* only few elements were able to actively move into acid before 3.1.0 */
if (level->game_version < VERSION_IDENT(3,1,0,0))
{
int i, j;
}
}
}
+
+#if 1 /* USE_NEW_BLOCK_STYLE */
+ /* blocking the last field when moving was corrected in version 3.1.1 */
+ if (level->game_version < VERSION_IDENT(3,1,1,0))
+ {
+#if 0
+ printf("::: %d\n", level->block_last_field);
+#endif
+
+ /* even "not blocking" was blocking the last field for one frame */
+ level->block_delay = (level->block_last_field ? 7 : 1);
+ level->sp_block_delay = (level->sp_block_last_field ? 7 : 1);
+
+ level->block_last_field = TRUE;
+ level->sp_block_last_field = TRUE;
+ }
+#endif
}
- else
+ else /* always use the latest game engine version */
{
#if 0
printf("\n::: ALWAYS USE LATEST ENGINE FOR THIS LEVEL: [%d] '%s'\n",
}
}
+ /* initialize "can_explode" field for old levels which did not store this */
+ /* !!! CHECK THIS -- "<= 3,1,0,0" IS PROBABLY WRONG !!! */
+ if (level->game_version <= VERSION_IDENT(3,1,0,0))
+ {
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+
+ if (EXPLODES_1X1_OLD(element))
+ element_info[element].explosion_type = EXPLODES_1X1;
+
+ SET_PROPERTY(element, EP_CAN_EXPLODE, (EXPLODES_BY_FIRE(element) ||
+ EXPLODES_SMASHED(element) ||
+ EXPLODES_IMPACT(element)));
+ }
+ }
+
+ /* correct previously hard-coded move delay values for maze runner style */
+ if (level->game_version < VERSION_IDENT(3,1,1,0))
+ {
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+
+ if (element_info[element].move_pattern & MV_MAZE_RUNNER_STYLE)
+ {
+ /* previously hard-coded and therefore ignored */
+ element_info[element].move_delay_fixed = 9;
+ element_info[element].move_delay_random = 0;
+ }
+ }
+ }
+
#if 0
/* set default push delay values (corrected since version 3.0.7-1) */
if (level->game_version < VERSION_IDENT(3,0,7,1))
LoadLevelFromFilename_RND(&level_template, filename);
#endif
+#if 1
+ LoadLevel_InitVersion(&level_template, filename);
+ LoadLevel_InitElements(&level_template, filename);
+#else
LoadLevel_InitVersion(&level, filename);
LoadLevel_InitElements(&level, filename);
+#endif
ActivateLevelTemplate();
}
putFile8Bit(file, (level->instant_relocation ? 1 : 0));
putFile8Bit(file, (level->can_pass_to_walkable ? 1 : 0));
+ putFile8Bit(file, (level->grow_into_diggable ? 1 : 0));
WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
}
putFile8Bit(file, ei->explosion_delay);
putFile8Bit(file, ei->ignition_delay);
+ putFile8Bit(file, ei->explosion_type);
/* some free bytes for future custom property values and padding */
- WriteUnusedBytesToFile(file, 2);
+ WriteUnusedBytesToFile(file, 1);
/* write change property values */
printf_line("-", 79);
printf("Tape of Level %03d (file version %08d, game version %08d)\n",
tape->level_nr, tape->file_version, tape->game_version);
+ printf(" (effective engine version %08d)\n",
+ tape->engine_version);
printf("Level series identifier: '%s'\n", tape->level_identifier);
printf_line("-", 79);
END_HASH_ITERATION(hash, itr)
#endif
}
+
+
+/* ------------------------------------------------------------------------- *
+ * convert levels
+ * ------------------------------------------------------------------------- */
+
+#define MAX_NUM_CONVERT_LEVELS 1000
+
+void ConvertLevels()
+{
+ static LevelDirTree *convert_leveldir = NULL;
+ static int convert_level_nr = -1;
+ static int num_levels_handled = 0;
+ static int num_levels_converted = 0;
+ static boolean levels_failed[MAX_NUM_CONVERT_LEVELS];
+ int i;
+
+ convert_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+ global.convert_leveldir);
+
+ if (convert_leveldir == NULL)
+ Error(ERR_EXIT, "no such level identifier: '%s'",
+ global.convert_leveldir);
+
+ leveldir_current = convert_leveldir;
+
+ if (global.convert_level_nr != -1)
+ {
+ convert_leveldir->first_level = global.convert_level_nr;
+ convert_leveldir->last_level = global.convert_level_nr;
+ }
+
+ convert_level_nr = convert_leveldir->first_level;
+
+ printf_line("=", 79);
+ printf("Converting levels\n");
+ printf_line("-", 79);
+ printf("Level series identifier: '%s'\n", convert_leveldir->identifier);
+ printf("Level series name: '%s'\n", convert_leveldir->name);
+ printf("Level series author: '%s'\n", convert_leveldir->author);
+ printf("Number of levels: %d\n", convert_leveldir->levels);
+ printf_line("=", 79);
+ printf("\n");
+
+ for (i = 0; i < MAX_NUM_CONVERT_LEVELS; i++)
+ levels_failed[i] = FALSE;
+
+ while (convert_level_nr <= convert_leveldir->last_level)
+ {
+ char *level_filename;
+ boolean new_level;
+
+ level_nr = convert_level_nr++;
+
+ printf("Level %03d: ", level_nr);
+
+ LoadLevel(level_nr);
+ if (level.no_valid_file)
+ {
+ printf("(no level)\n");
+ continue;
+ }
+
+ printf("converting level ... ");
+
+ level_filename = getDefaultLevelFilename(level_nr);
+ new_level = !fileExists(level_filename);
+
+ if (new_level)
+ {
+ SaveLevel(level_nr);
+
+ num_levels_converted++;
+
+ printf("converted.\n");
+ }
+ else
+ {
+ if (level_nr >= 0 && level_nr < MAX_NUM_CONVERT_LEVELS)
+ levels_failed[level_nr] = TRUE;
+
+ printf("NOT CONVERTED -- LEVEL ALREADY EXISTS.\n");
+ }
+
+ num_levels_handled++;
+ }
+
+ printf("\n");
+ printf_line("=", 79);
+ printf("Number of levels handled: %d\n", num_levels_handled);
+ printf("Number of levels converted: %d (%d%%)\n", num_levels_converted,
+ (num_levels_handled ?
+ num_levels_converted * 100 / num_levels_handled : 0));
+ printf_line("-", 79);
+ printf("Summary (for automatic parsing by scripts):\n");
+ printf("LEVELDIR '%s', CONVERTED %d/%d (%d%%)",
+ convert_leveldir->identifier, num_levels_converted,
+ num_levels_handled,
+ (num_levels_handled ?
+ num_levels_converted * 100 / num_levels_handled : 0));
+
+ if (num_levels_handled != num_levels_converted)
+ {
+ printf(", FAILED:");
+ for (i = 0; i < MAX_NUM_CONVERT_LEVELS; i++)
+ if (levels_failed[i])
+ printf(" %03d", i);
+ }
+
+ printf("\n");
+ printf_line("=", 79);
+
+ CloseAllAndExit(0);
+}