X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=ccea3f949f2f645f55ad17ff4d95bef93bb48982;hb=1442f47fb19411923cba74dd8a7c17fef1fe5e0d;hp=dd60167475030a9d34b45dce8d4937516b157944;hpb=a507c4034ee989815fcf9b325d5be1d17c29bb57;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index dd601674..ccea3f94 100644 --- a/src/files.c +++ b/src/files.c @@ -29,7 +29,7 @@ #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 11 /* 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 */ @@ -92,7 +92,10 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) change->can_change = FALSE; change->events = CE_BITMASK_DEFAULT; - change->sides = CH_SIDE_ANY; + + change->trigger_player = CH_PLAYER_ANY; + change->trigger_side = CH_SIDE_ANY; + change->trigger_page = CH_PAGE_ANY; change->target_element = EL_EMPTY_SPACE; @@ -103,15 +106,15 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) change->trigger_element = EL_EMPTY_SPACE; change->explode = FALSE; - change->use_content = FALSE; - change->only_complete = FALSE; - change->use_random_change = FALSE; - change->random = 100; - change->power = CP_NON_DESTRUCTIVE; + change->use_target_content = FALSE; + change->only_if_complete = FALSE; + change->use_random_replace = FALSE; + change->random_percentage = 100; + change->replace_when = CP_WHEN_EMPTY; for (x = 0; x < 3; x++) for (y = 0; y < 3; y++) - change->content[x][y] = EL_EMPTY_SPACE; + change->target_content[x][y] = EL_EMPTY_SPACE; change->direct_action = 0; change->other_action = 0; @@ -123,6 +126,8 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) static void setLevelInfoToDefaults(struct LevelInfo *level) { + static boolean clipboard_elements_initialized = FALSE; + int i, j, x, y; level->file_version = FILE_VERSION_ACTUAL; @@ -141,17 +146,30 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) level->time = 100; level->gems_needed = 0; + level->amoeba_speed = 10; + level->time_magic_wall = 10; level->time_wheel = 10; level->time_light = 10; level->time_timegate = 10; + level->amoeba_content = EL_DIAMOND; + 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->can_move_into_acid_bits = ~0; /* everything can move into acid */ + level->dont_collide_with_bits = ~0; /* always deadly when colliding */ + + level->use_spring_bug = FALSE; + level->use_step_counter = FALSE; level->use_custom_template = FALSE; @@ -187,10 +205,16 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) { int element = i; + /* never initialize clipboard elements after the very first time */ + if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized) + continue; + setElementChangePages(&element_info[element], 1); setElementChangeInfoToDefaults(element_info[element].change); - if (IS_CUSTOM_ELEMENT(element) || IS_GROUP_ELEMENT(element)) + if (IS_CUSTOM_ELEMENT(element) || + IS_GROUP_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) { for (j = 0; j < MAX_ELEMENT_NAME_LEN + 1; j++) element_info[element].description[j] = '\0'; @@ -204,37 +228,49 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) 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)) + if (IS_CUSTOM_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) { + element_info[element].access_direction = MV_ALL_DIRECTIONS; + element_info[element].collect_score = 10; /* special default */ element_info[element].collect_count = 1; /* special default */ element_info[element].push_delay_fixed = -1; /* initialize later */ element_info[element].push_delay_random = -1; /* initialize later */ + element_info[element].drop_delay_fixed = 0; + element_info[element].drop_delay_random = 0; element_info[element].move_delay_fixed = 0; element_info[element].move_delay_random = 0; element_info[element].move_pattern = MV_ALL_DIRECTIONS; - element_info[element].move_direction_initial = MV_AUTOMATIC; + 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; + for (x = 0; x < 3; x++) for (y = 0; y < 3; y++) element_info[element].content[x][y] = EL_EMPTY_SPACE; element_info[element].access_type = 0; element_info[element].access_layer = 0; + element_info[element].access_protected = 0; 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; @@ -246,9 +282,12 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) for (j = 0; j < NUM_EP_BITFIELDS; j++) Properties[element][j] = EP_BITMASK_DEFAULT; - element_info[element].modified_settings = FALSE; + /* now set default properties */ + SET_PROPERTY(element, EP_CAN_MOVE_INTO_ACID, TRUE); } - else if (IS_GROUP_ELEMENT(element) || element == EL_INTERNAL_EDITOR) + + if (IS_GROUP_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) { /* initialize memory for list of elements in group */ if (element_info[element].group == NULL) @@ -265,9 +304,11 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) } } + clipboard_elements_initialized = TRUE; + BorderElement = EL_STEELWALL; - level->no_level_file = FALSE; + level->no_valid_file = FALSE; if (leveldir_current == NULL) /* only when dumping level */ return; @@ -527,7 +568,7 @@ static struct LevelFileInfo *getLevelFileInfo(int nr) int getMappedElement(int element) { - /* map some (historic, now obsolete) elements */ + /* remap some (historic, now obsolete) elements */ #if 1 switch (element) @@ -568,7 +609,7 @@ int getMappedElement(int element) { Error(ERR_WARN, "invalid level element %d", element); - element = EL_CHAR_QUESTION; + element = EL_UNKNOWN; } break; } @@ -577,7 +618,7 @@ int getMappedElement(int element) { Error(ERR_WARN, "invalid level element %d", element); - element = EL_CHAR_QUESTION; + element = EL_UNKNOWN; } else if (element == EL_PLAYER_OBSOLETE) element = EL_PLAYER_1; @@ -588,6 +629,32 @@ int getMappedElement(int element) return element; } +int getMappedElementByVersion(int element, int game_version) +{ + /* remap some elements due to certain game version */ + + if (game_version <= VERSION_IDENT(2,2,0,0)) + { + /* map game font elements */ + element = (element == EL_CHAR('[') ? EL_CHAR_AUMLAUT : + element == EL_CHAR('\\') ? EL_CHAR_OUMLAUT : + element == EL_CHAR(']') ? EL_CHAR_UUMLAUT : + element == EL_CHAR('^') ? EL_CHAR_COPYRIGHT : element); + } + + if (game_version < VERSION_IDENT(3,0,0,0)) + { + /* map Supaplex gravity tube elements */ + element = (element == EL_SP_GRAVITY_PORT_LEFT ? EL_SP_PORT_LEFT : + element == EL_SP_GRAVITY_PORT_RIGHT ? EL_SP_PORT_RIGHT : + element == EL_SP_GRAVITY_PORT_UP ? EL_SP_PORT_UP : + element == EL_SP_GRAVITY_PORT_DOWN ? EL_SP_PORT_DOWN : + element); + } + + return element; +} + static int LoadLevel_VERS(FILE *file, int chunk_size, struct LevelInfo *level) { level->file_version = getFileVersion(file); @@ -632,6 +699,15 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level) level->block_last_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); level->sp_block_last_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->can_move_into_acid_bits = getFile32BitBE(file); + level->dont_collide_with_bits = getFile8Bit(file); + + level->use_spring_bug = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->use_step_counter = (getFile8Bit(file) == 1 ? TRUE : FALSE); + + 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); @@ -912,16 +988,16 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level) getMappedElement(getFile16BitBE(file)); element_info[element].change->explode = getFile8Bit(file); - element_info[element].change->use_content = getFile8Bit(file); - element_info[element].change->only_complete = getFile8Bit(file); - element_info[element].change->use_random_change = getFile8Bit(file); + element_info[element].change->use_target_content = getFile8Bit(file); + element_info[element].change->only_if_complete = getFile8Bit(file); + element_info[element].change->use_random_replace = getFile8Bit(file); - element_info[element].change->random = getFile8Bit(file); - element_info[element].change->power = getFile8Bit(file); + element_info[element].change->random_percentage = getFile8Bit(file); + element_info[element].change->replace_when = getFile8Bit(file); for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - element_info[element].change->content[x][y] = + element_info[element].change->target_content[x][y] = getMappedElement(getFile16BitBE(file)); element_info[element].slippery_type = getFile8Bit(file); @@ -982,11 +1058,14 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level) ei->collect_score = getFile8Bit(file); ei->collect_count = getFile8Bit(file); - ei->push_delay_fixed = getFile16BitBE(file); - ei->push_delay_random = getFile16BitBE(file); + ei->drop_delay_fixed = getFile8Bit(file); + ei->push_delay_fixed = getFile8Bit(file); + ei->drop_delay_random = getFile8Bit(file); + ei->push_delay_random = getFile8Bit(file); ei->move_delay_fixed = getFile16BitBE(file); ei->move_delay_random = getFile16BitBE(file); + /* bits 0 - 15 of "move_pattern" ... */ ei->move_pattern = getFile16BitBE(file); ei->move_direction_initial = getFile8Bit(file); ei->move_stepsize = getFile8Bit(file); @@ -1001,8 +1080,17 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level) ei->move_leave_element = getMappedElement(getFile16BitBE(file)); ei->move_leave_type = getFile8Bit(file); + /* ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) */ + ei->move_pattern |= (getFile16BitBE(file) << 16); + + ei->access_direction = getFile8Bit(file); + + 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, 7); + ReadUnusedBytesFromFile(file, 1); /* read change property values */ @@ -1026,26 +1114,36 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level) change->trigger_element = getMappedElement(getFile16BitBE(file)); change->explode = getFile8Bit(file); - change->use_content = getFile8Bit(file); - change->only_complete = getFile8Bit(file); - change->use_random_change = getFile8Bit(file); + change->use_target_content = getFile8Bit(file); + change->only_if_complete = getFile8Bit(file); + change->use_random_replace = getFile8Bit(file); - change->random = getFile8Bit(file); - change->power = getFile8Bit(file); + change->random_percentage = getFile8Bit(file); + change->replace_when = getFile8Bit(file); for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - change->content[x][y] = getMappedElement(getFile16BitBE(file)); + change->target_content[x][y] = getMappedElement(getFile16BitBE(file)); change->can_change = getFile8Bit(file); - change->sides = getFile8Bit(file); + change->trigger_side = getFile8Bit(file); - if (change->sides == CH_SIDE_NONE) /* correct empty sides field */ - change->sides = CH_SIDE_ANY; +#if 1 + change->trigger_player = getFile8Bit(file); + change->trigger_page = getFile8Bit(file); + + change->trigger_page = (change->trigger_page == CH_PAGE_ANY_FILE ? + CH_PAGE_ANY : (1 << change->trigger_page)); + + /* some free bytes for future change property values and padding */ + ReadUnusedBytesFromFile(file, 6); + +#else /* some free bytes for future change property values and padding */ ReadUnusedBytesFromFile(file, 8); +#endif } /* mark this custom element as modified */ @@ -1109,7 +1207,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (!(file = fopen(filename, MODE_READ))) { - level->no_level_file = TRUE; + level->no_valid_file = TRUE; if (level != &level_template) Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); @@ -1125,6 +1223,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, getFileChunkBE(file, chunk_name, NULL); if (strcmp(chunk_name, "CAVE") != 0) { + level->no_valid_file = TRUE; + Error(ERR_WARN, "unknown format of level file '%s'", filename); fclose(file); return; @@ -1139,6 +1239,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL)) { + level->no_valid_file = TRUE; + Error(ERR_WARN, "unknown format of level file '%s'", filename); fclose(file); return; @@ -1146,6 +1248,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if ((level->file_version = getFileVersionFromCookieString(cookie)) == -1) { + level->no_valid_file = TRUE; + Error(ERR_WARN, "unsupported version of level file '%s'", filename); fclose(file); return; @@ -1277,7 +1381,7 @@ static int map_em_element_yam(int element) default: Error(ERR_WARN, "invalid level element %d", element); - return EL_CHAR_QUESTION; + return EL_UNKNOWN; } } @@ -1291,10 +1395,13 @@ static int map_em_element_field(int element) switch (element) { case 0x00: return EL_ROCK; + case 0x01: return EL_ROCK; /* EMC */ case 0x02: return EL_DIAMOND; case 0x03: return EL_DIAMOND; case 0x04: return EL_ROBOT; case 0x05: return EL_ROBOT; /* EMC */ + case 0x06: return EL_EMPTY_SPACE; /* EMC */ + case 0x07: return EL_EMPTY_SPACE; /* EMC */ case 0x08: return EL_SPACESHIP_UP; case 0x09: return EL_SPACESHIP_RIGHT; case 0x0a: return EL_SPACESHIP_DOWN; @@ -1303,7 +1410,9 @@ static int map_em_element_field(int element) case 0x0d: return EL_SPACESHIP_RIGHT; case 0x0e: return EL_SPACESHIP_DOWN; case 0x0f: return EL_SPACESHIP_LEFT; + case 0x10: return EL_BOMB; + case 0x11: return EL_BOMB; /* EMC */ case 0x12: return EL_EMERALD; case 0x13: return EL_EMERALD; case 0x14: return EL_BUG_UP; @@ -1315,34 +1424,115 @@ static int map_em_element_field(int element) case 0x1a: return EL_BUG_DOWN; case 0x1b: return EL_BUG_LEFT; case 0x1c: return EL_AMOEBA_DROP; + case 0x1d: return EL_AMOEBA_DROP; /* EMC */ + case 0x1e: return EL_AMOEBA_DROP; /* EMC */ + case 0x1f: return EL_AMOEBA_DROP; /* EMC */ + case 0x20: return EL_ROCK; + case 0x21: return EL_BOMB; /* EMC */ + case 0x22: return EL_DIAMOND; /* EMC */ + case 0x23: return EL_EMERALD; /* EMC */ case 0x24: return EL_MAGIC_WALL; case 0x25: return EL_NUT; + case 0x26: return EL_NUT; /* EMC */ + case 0x27: return EL_NUT; /* EMC */ /* looks like magic wheel, but is _always_ activated */ case 0x28: return EL_ROBOT_WHEEL; /* EMC */ - case 0x29: return EL_YAMYAM; - case 0x2a: return EL_YAMYAM; - case 0x2b: return EL_YAMYAM; /* EMC */ - case 0x2c: return EL_YAMYAM; /* EMC */ + case 0x29: return EL_YAMYAM; /* up */ + case 0x2a: return EL_YAMYAM; /* down */ + case 0x2b: return EL_YAMYAM; /* left */ /* EMC */ + case 0x2c: return EL_YAMYAM; /* right */ /* EMC */ case 0x2d: return EL_QUICKSAND_FULL; + case 0x2e: return EL_EMPTY_SPACE; /* EMC */ + case 0x2f: return EL_EMPTY_SPACE; /* EMC */ + + case 0x30: return EL_EMPTY_SPACE; /* EMC */ + case 0x31: return EL_SAND; /* EMC */ + case 0x32: return EL_SAND; /* EMC */ + case 0x33: return EL_SAND; /* EMC */ + case 0x34: return EL_QUICKSAND_FULL; /* EMC */ + case 0x35: return EL_QUICKSAND_FULL; /* EMC */ + case 0x36: return EL_QUICKSAND_FULL; /* EMC */ + case 0x37: return EL_SAND; /* EMC */ + case 0x38: return EL_ROCK; /* EMC */ case 0x39: return EL_EXPANDABLE_WALL_HORIZONTAL; /* EMC */ case 0x3a: return EL_EXPANDABLE_WALL_VERTICAL; /* EMC */ - case 0x3b: return EL_DYNAMITE_ACTIVE; - case 0x3c: return EL_DYNAMITE_ACTIVE; - case 0x3d: return EL_DYNAMITE_ACTIVE; - case 0x3e: return EL_DYNAMITE_ACTIVE; + case 0x3b: return EL_DYNAMITE_ACTIVE; /* 1 */ + case 0x3c: return EL_DYNAMITE_ACTIVE; /* 2 */ + case 0x3d: return EL_DYNAMITE_ACTIVE; /* 3 */ + case 0x3e: return EL_DYNAMITE_ACTIVE; /* 4 */ case 0x3f: return EL_ACID_POOL_BOTTOM; - case 0x40: return EL_EXIT_OPEN; - case 0x41: return EL_EXIT_OPEN; - case 0x42: return EL_EXIT_OPEN; - case 0x43: return EL_BALLOON; - case 0x4e: return EL_INVISIBLE_WALL; - case 0x65: return EL_ACID; /* EMC */ - case 0x73: return EL_SAND; /* EMC */ + + case 0x40: return EL_EXIT_OPEN; /* 1 */ + case 0x41: return EL_EXIT_OPEN; /* 2 */ + case 0x42: return EL_EXIT_OPEN; /* 3 */ + case 0x43: return EL_BALLOON; /* EMC */ + case 0x44: return EL_UNKNOWN; /* EMC ("plant") */ + case 0x45: return EL_SPRING; /* EMC */ + case 0x46: return EL_SPRING; /* falling */ /* EMC */ + case 0x47: return EL_SPRING; /* left */ /* EMC */ + case 0x48: return EL_SPRING; /* right */ /* EMC */ + case 0x49: return EL_UNKNOWN; /* EMC ("ball 1") */ + case 0x4a: return EL_UNKNOWN; /* EMC ("ball 2") */ + case 0x4b: return EL_UNKNOWN; /* EMC ("android") */ + case 0x4c: return EL_EMPTY_SPACE; /* EMC */ + case 0x4d: return EL_UNKNOWN; /* EMC ("android") */ + case 0x4e: return EL_INVISIBLE_WALL; /* EMC (? "android") */ + case 0x4f: return EL_UNKNOWN; /* EMC ("android") */ + + case 0x50: return EL_UNKNOWN; /* EMC ("android") */ + case 0x51: return EL_UNKNOWN; /* EMC ("android") */ + case 0x52: return EL_UNKNOWN; /* EMC ("android") */ + case 0x53: return EL_UNKNOWN; /* EMC ("android") */ + case 0x54: return EL_UNKNOWN; /* EMC ("android") */ + case 0x55: return EL_EMPTY_SPACE; /* EMC */ + case 0x56: return EL_EMPTY_SPACE; /* EMC */ + case 0x57: return EL_EMPTY_SPACE; /* EMC */ + case 0x58: return EL_EMPTY_SPACE; /* EMC */ + case 0x59: return EL_EMPTY_SPACE; /* EMC */ + case 0x5a: return EL_EMPTY_SPACE; /* EMC */ + case 0x5b: return EL_EMPTY_SPACE; /* EMC */ + case 0x5c: return EL_EMPTY_SPACE; /* EMC */ + case 0x5d: return EL_EMPTY_SPACE; /* EMC */ + case 0x5e: return EL_EMPTY_SPACE; /* EMC */ + case 0x5f: return EL_EMPTY_SPACE; /* EMC */ + + case 0x60: return EL_EMPTY_SPACE; /* EMC */ + case 0x61: return EL_EMPTY_SPACE; /* EMC */ + case 0x62: return EL_EMPTY_SPACE; /* EMC */ + case 0x63: return EL_SPRING; /* left */ /* EMC */ + case 0x64: return EL_SPRING; /* right */ /* EMC */ + case 0x65: return EL_ACID; /* 1 */ /* EMC */ + case 0x66: return EL_ACID; /* 2 */ /* EMC */ + case 0x67: return EL_ACID; /* 3 */ /* EMC */ + case 0x68: return EL_ACID; /* 4 */ /* EMC */ + case 0x69: return EL_ACID; /* 5 */ /* EMC */ + case 0x6a: return EL_ACID; /* 6 */ /* EMC */ + case 0x6b: return EL_ACID; /* 7 */ /* EMC */ + case 0x6c: return EL_ACID; /* 8 */ /* EMC */ + case 0x6d: return EL_EMPTY_SPACE; /* EMC */ + case 0x6e: return EL_EMPTY_SPACE; /* EMC */ + case 0x6f: return EL_EMPTY_SPACE; /* EMC */ + + case 0x70: return EL_EMPTY_SPACE; /* EMC */ + case 0x71: return EL_EMPTY_SPACE; /* EMC */ + case 0x72: return EL_NUT; /* left */ /* EMC */ + case 0x73: return EL_SAND; /* EMC (? "nut") */ case 0x74: return EL_STEELWALL; - case 0x7b: return EL_ACID; + case 0x75: return EL_EMPTY_SPACE; /* EMC */ + case 0x76: return EL_EMPTY_SPACE; /* EMC */ + case 0x77: return EL_BOMB; /* left */ /* EMC */ + case 0x78: return EL_BOMB; /* right */ /* EMC */ + case 0x79: return EL_ROCK; /* left */ /* EMC */ + case 0x7a: return EL_ROCK; /* right */ /* EMC */ + case 0x7b: return EL_ACID; /* (? EMC "blank") */ + case 0x7c: return EL_EMPTY_SPACE; /* EMC */ + case 0x7d: return EL_EMPTY_SPACE; /* EMC */ + case 0x7e: return EL_EMPTY_SPACE; /* EMC */ + case 0x7f: return EL_EMPTY_SPACE; /* EMC */ + case 0x80: return EL_EMPTY; case 0x81: return EL_WALL_SLIPPERY; case 0x82: return EL_SAND; @@ -1356,63 +1546,101 @@ static int map_em_element_field(int element) case 0x8a: return EL_EM_GATE_2; case 0x8b: return EL_EM_GATE_4; case 0x8c: return EL_EM_GATE_3; - case 0x8d: return EL_INVISIBLE_WALL; /* EMC */ + case 0x8d: return EL_INVISIBLE_WALL; /* EMC (? "dripper") */ case 0x8e: return EL_EM_GATE_1_GRAY; case 0x8f: return EL_EM_GATE_2_GRAY; + case 0x90: return EL_EM_GATE_4_GRAY; case 0x91: return EL_EM_GATE_3_GRAY; case 0x92: return EL_MAGIC_WALL; - case 0x94: return EL_QUICKSAND_EMPTY; + case 0x93: return EL_ROBOT_WHEEL; + case 0x94: return EL_QUICKSAND_EMPTY; /* (? EMC "sand") */ case 0x95: return EL_ACID_POOL_TOPLEFT; case 0x96: return EL_ACID_POOL_TOPRIGHT; case 0x97: return EL_ACID_POOL_BOTTOMLEFT; case 0x98: return EL_ACID_POOL_BOTTOMRIGHT; - case 0x99: return EL_ACID; - case 0x9a: return EL_AMOEBA_DEAD; - case 0x9b: return EL_AMOEBA_DEAD; - case 0x9c: return EL_AMOEBA_DEAD; - case 0x9d: return EL_AMOEBA_DEAD; + case 0x99: return EL_ACID; /* (? EMC "fake blank") */ + case 0x9a: return EL_AMOEBA_DEAD; /* 1 */ + case 0x9b: return EL_AMOEBA_DEAD; /* 2 */ + case 0x9c: return EL_AMOEBA_DEAD; /* 3 */ + case 0x9d: return EL_AMOEBA_DEAD; /* 4 */ case 0x9e: return EL_EXIT_CLOSED; - case 0x9f: return EL_CHAR_LESS; /* EMC */ - case 0x93: return EL_ROBOT_WHEEL; - - /* looks like normal dust, but behaves like wall */ - case 0xa0: return EL_WALL; /* EMC */ - - case 0xa8: return EL_EMC_WALL_1; /* EMC */ - case 0xa9: return EL_EMC_WALL_2; /* EMC */ - case 0xaa: return EL_EMC_WALL_3; /* EMC */ - case 0xab: return EL_EMC_WALL_7; /* EMC */ + case 0x9f: return EL_CHAR_LESS; /* arrow left */ + + /* looks like normal sand, but behaves like wall */ + case 0xa0: return EL_UNKNOWN; /* EMC ("fake grass") */ + case 0xa1: return EL_UNKNOWN; /* EMC ("lenses") */ + case 0xa2: return EL_UNKNOWN; /* EMC ("magnify") */ + case 0xa3: return EL_UNKNOWN; /* EMC ("fake blank") */ + case 0xa4: return EL_UNKNOWN; /* EMC ("fake grass") */ + case 0xa5: return EL_UNKNOWN; /* EMC ("switch") */ + case 0xa6: return EL_UNKNOWN; /* EMC ("switch") */ + case 0xa7: return EL_EMPTY_SPACE; /* EMC */ + case 0xa8: return EL_EMC_WALL_1; /* EMC ("decor 8") */ + case 0xa9: return EL_EMC_WALL_2; /* EMC ("decor 9") */ + case 0xaa: return EL_EMC_WALL_3; /* EMC ("decor 10") */ + case 0xab: return EL_EMC_WALL_7; /* EMC ("decor 5") */ + case 0xac: return EL_CHAR_COMMA; /* EMC */ + case 0xad: return EL_CHAR_QUOTEDBL; /* EMC */ case 0xae: return EL_CHAR_MINUS; /* EMC */ case 0xaf: return EL_DYNAMITE; - case 0xb0: return EL_EMC_STEELWALL_1; /* EMC */ - case 0xb1: return EL_EMC_WALL_8; /* EMC */ - /* (exact steel wall) */ - case 0xb3: return EL_STEELWALL; /* EMC */ - - case 0xb4: return EL_WALL_SLIPPERY; /* EMC */ - case 0xb5: return EL_EMC_WALL_6; /* EMC */ - case 0xb6: return EL_EMC_WALL_5; /* EMC */ - case 0xb7: return EL_EMC_WALL_4; /* EMC */ + case 0xb0: return EL_EMC_STEELWALL_1; /* EMC ("steel 3") */ + case 0xb1: return EL_EMC_WALL_8; /* EMC ("decor 6") */ + case 0xb2: return EL_UNKNOWN; /* EMC ("decor 7") */ + case 0xb3: return EL_STEELWALL; /* 2 */ /* EMC */ + case 0xb4: return EL_WALL_SLIPPERY; /* 2 */ /* EMC */ + case 0xb5: return EL_EMC_WALL_6; /* EMC ("decor 2") */ + case 0xb6: return EL_EMC_WALL_5; /* EMC ("decor 4") */ + case 0xb7: return EL_EMC_WALL_4; /* EMC ("decor 3") */ case 0xb8: return EL_BALLOON_SWITCH_ANY; /* EMC */ case 0xb9: return EL_BALLOON_SWITCH_RIGHT; /* EMC */ case 0xba: return EL_BALLOON_SWITCH_DOWN; /* EMC */ case 0xbb: return EL_BALLOON_SWITCH_LEFT; /* EMC */ case 0xbc: return EL_BALLOON_SWITCH_UP; /* EMC */ - case 0xbd: return EL_SAND; /* EMC */ + case 0xbd: return EL_SAND; /* EMC ("dirt") */ + case 0xbe: return EL_UNKNOWN; /* EMC ("plant") */ + case 0xbf: return EL_UNKNOWN; /* EMC ("key 5") */ + + case 0xc0: return EL_UNKNOWN; /* EMC ("key 6") */ + case 0xc1: return EL_UNKNOWN; /* EMC ("key 7") */ + case 0xc2: return EL_UNKNOWN; /* EMC ("key 8") */ + case 0xc3: return EL_UNKNOWN; /* EMC ("door 5") */ + case 0xc4: return EL_UNKNOWN; /* EMC ("door 6") */ + case 0xc5: return EL_UNKNOWN; /* EMC ("door 7") */ + case 0xc6: return EL_UNKNOWN; /* EMC ("door 8") */ + case 0xc7: return EL_UNKNOWN; /* EMC ("bumper") */ + + /* characters: see above */ + case 0xec: return EL_CHAR_PERIOD; case 0xed: return EL_CHAR_EXCLAM; case 0xee: return EL_CHAR_COLON; case 0xef: return EL_CHAR_QUESTION; - case 0xf0: return EL_CHAR_GREATER; - case 0xf1: return EL_CHAR_COPYRIGHT; - case 0xfe: return EL_PLAYER_1; - case 0xff: return EL_PLAYER_2; + + case 0xf0: return EL_CHAR_GREATER; /* arrow right */ + case 0xf1: return EL_CHAR_COPYRIGHT; /* EMC: "decor 1" */ + case 0xf2: return EL_UNKNOWN; /* EMC ("fake door 5") */ + case 0xf3: return EL_UNKNOWN; /* EMC ("fake door 6") */ + case 0xf4: return EL_UNKNOWN; /* EMC ("fake door 7") */ + case 0xf5: return EL_UNKNOWN; /* EMC ("fake door 8") */ + case 0xf6: return EL_EMPTY_SPACE; /* EMC */ + case 0xf7: return EL_EMPTY_SPACE; /* EMC */ + + case 0xf8: return EL_EMPTY_SPACE; /* EMC */ + case 0xf9: return EL_EMPTY_SPACE; /* EMC */ + case 0xfa: return EL_EMPTY_SPACE; /* EMC */ + case 0xfb: return EL_EMPTY_SPACE; /* EMC */ + case 0xfc: return EL_EMPTY_SPACE; /* EMC */ + case 0xfd: return EL_EMPTY_SPACE; /* EMC */ + + case 0xfe: return EL_PLAYER_1; /* EMC: "blank" */ + case 0xff: return EL_PLAYER_2; /* EMC: "blank" */ default: + /* should never happen (all 8-bit value cases should be handled) */ Error(ERR_WARN, "invalid level element %d", element); - return EL_CHAR_QUESTION; + return EL_UNKNOWN; } } @@ -1435,7 +1663,7 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level, if (!(file = fopen(filename, MODE_READ))) { - level->no_level_file = TRUE; + level->no_valid_file = TRUE; Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); @@ -1561,7 +1789,7 @@ static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level, Error(ERR_WARN, "in level %d, at position %d, %d:", nr, x, y); Error(ERR_WARN, "invalid level element %d", element_old); - element_new = EL_CHAR_QUESTION; + element_new = EL_UNKNOWN; } level->field[x][y] = element_new; @@ -1627,7 +1855,7 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level, if (!(file = fopen(filename, MODE_READ))) { - level->no_level_file = TRUE; + level->no_valid_file = TRUE; Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); @@ -1637,7 +1865,7 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level, /* position file stream to the requested level inside the level package */ if (fseek(file, nr * SP_LEVEL_SIZE, SEEK_SET) != 0) { - level->no_level_file = TRUE; + level->no_valid_file = TRUE; Error(ERR_WARN, "cannot fseek level '%s' -- using empty level", filename); @@ -1869,6 +2097,11 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename) 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) { @@ -1878,10 +2111,44 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename) /* 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; + + if (level->game_version < VERSION_IDENT(2,2,0,0)) + level->use_spring_bug = TRUE; + + if (level->game_version < VERSION_IDENT(3,1,0,0)) + { + int i, j; + + level->can_move_into_acid_bits = 0; /* nothing can move into acid */ + level->dont_collide_with_bits = 0; /* nothing is deadly when colliding */ + + setMoveIntoAcidProperty(level, EL_ROBOT, TRUE); + setMoveIntoAcidProperty(level, EL_SATELLITE, TRUE); + setMoveIntoAcidProperty(level, EL_PENGUIN, TRUE); + setMoveIntoAcidProperty(level, EL_BALLOON, TRUE); + + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + SET_PROPERTY(EL_CUSTOM_START + i, EP_CAN_MOVE_INTO_ACID, TRUE); + + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; + + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; + + change->trigger_player = CH_PLAYER_ANY; + change->trigger_page = CH_PAGE_ANY; + } + } + } } else { @@ -1927,7 +2194,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename) static void LoadLevel_InitElements(struct LevelInfo *level, char *filename) { - int i, j; + int i, j, x, y; /* map custom element change events that have changed in newer versions (these following values were accidentally changed in version 3.0.1) */ @@ -1986,6 +2253,40 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename) } } + /* correct custom element values (for old levels without these options) */ + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; + + if (ei->access_direction == MV_NO_MOVING) + ei->access_direction = MV_ALL_DIRECTIONS; + + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (change->trigger_side == CH_SIDE_NONE) + change->trigger_side = CH_SIDE_ANY; + } + } + + /* initialize "can_explode" field for old levels which did not store this */ + 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))); + } + } + #if 0 /* set default push delay values (corrected since version 3.0.7-1) */ if (level->game_version < VERSION_IDENT(3,0,7,1)) @@ -2011,6 +2312,16 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename) } #endif + /* map elements that have changed in newer versions */ + level->amoeba_content = getMappedElementByVersion(level->amoeba_content, + level->game_version); + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (x = 0; x < 3; x++) + for (y = 0; y < 3; y++) + level->yamyam_content[i][x][y] = + getMappedElementByVersion(level->yamyam_content[i][x][y], + level->game_version); + /* initialize element properties for level editor etc. */ InitElementPropertiesEngine(level->game_version); } @@ -2026,6 +2337,9 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename) { int element = level->field[x][y]; +#if 1 + element = getMappedElementByVersion(element, level->game_version); +#else if (level->game_version <= VERSION_IDENT(2,2,0,0)) { /* map game font elements */ @@ -2044,6 +2358,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename) element == EL_SP_GRAVITY_PORT_DOWN ? EL_SP_PORT_DOWN : element); } +#endif level->field[x][y] = element; } @@ -2147,6 +2462,15 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level) putFile8Bit(file, (level->block_last_field ? 1 : 0)); putFile8Bit(file, (level->sp_block_last_field ? 1 : 0)); + putFile32BitBE(file, level->can_move_into_acid_bits); + putFile8Bit(file, level->dont_collide_with_bits); + + putFile8Bit(file, (level->use_spring_bug ? 1 : 0)); + putFile8Bit(file, (level->use_step_counter ? 1 : 0)); + + 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); } @@ -2373,12 +2697,12 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, putFile16BitBE(file, element_info[element].change->trigger_element); putFile8Bit(file, element_info[element].change->explode); - putFile8Bit(file, element_info[element].change->use_content); - putFile8Bit(file, element_info[element].change->only_complete); - putFile8Bit(file, element_info[element].change->use_random_change); + putFile8Bit(file, element_info[element].change->use_target_content); + putFile8Bit(file, element_info[element].change->only_if_complete); + putFile8Bit(file, element_info[element].change->use_random_replace); - putFile8Bit(file, element_info[element].change->random); - putFile8Bit(file, element_info[element].change->power); + putFile8Bit(file, element_info[element].change->random_percentage); + putFile8Bit(file, element_info[element].change->replace_when); for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) @@ -2425,12 +2749,15 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) putFile8Bit(file, ei->collect_score); putFile8Bit(file, ei->collect_count); - putFile16BitBE(file, ei->push_delay_fixed); - putFile16BitBE(file, ei->push_delay_random); + putFile8Bit(file, ei->drop_delay_fixed); + putFile8Bit(file, ei->push_delay_fixed); + putFile8Bit(file, ei->drop_delay_random); + putFile8Bit(file, ei->push_delay_random); putFile16BitBE(file, ei->move_delay_fixed); putFile16BitBE(file, ei->move_delay_random); - putFile16BitBE(file, ei->move_pattern); + /* bits 0 - 15 of "move_pattern" ... */ + putFile16BitBE(file, ei->move_pattern & 0xffff); putFile8Bit(file, ei->move_direction_initial); putFile8Bit(file, ei->move_stepsize); @@ -2444,8 +2771,17 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) putFile16BitBE(file, ei->move_leave_element); putFile8Bit(file, ei->move_leave_type); + /* ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) */ + putFile16BitBE(file, (ei->move_pattern >> 16) & 0xffff); + + putFile8Bit(file, ei->access_direction); + + 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, 7); + WriteUnusedBytesToFile(file, 1); /* write change property values */ @@ -2464,23 +2800,34 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) putFile16BitBE(file, change->trigger_element); putFile8Bit(file, change->explode); - putFile8Bit(file, change->use_content); - putFile8Bit(file, change->only_complete); - putFile8Bit(file, change->use_random_change); + putFile8Bit(file, change->use_target_content); + putFile8Bit(file, change->only_if_complete); + putFile8Bit(file, change->use_random_replace); - putFile8Bit(file, change->random); - putFile8Bit(file, change->power); + putFile8Bit(file, change->random_percentage); + putFile8Bit(file, change->replace_when); for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - putFile16BitBE(file, change->content[x][y]); + putFile16BitBE(file, change->target_content[x][y]); putFile8Bit(file, change->can_change); - putFile8Bit(file, change->sides); + putFile8Bit(file, change->trigger_side); + +#if 1 + putFile8Bit(file, change->trigger_player); + putFile8Bit(file, (change->trigger_page == CH_PAGE_ANY ? CH_PAGE_ANY_FILE : + log_2(change->trigger_page))); + + /* some free bytes for future change property values and padding */ + WriteUnusedBytesToFile(file, 6); + +#else /* some free bytes for future change property values and padding */ WriteUnusedBytesToFile(file, 8); +#endif } } @@ -2641,6 +2988,13 @@ void SaveLevelTemplate() void DumpLevel(struct LevelInfo *level) { + if (level->no_valid_file) + { + Error(ERR_WARN, "cannot dump -- no valid level file found"); + + return; + } + printf_line("-", 79); printf("Level xxx (file version %08d, game version %08d)\n", level->file_version, level->game_version); @@ -2666,6 +3020,8 @@ void DumpLevel(struct LevelInfo *level) printf("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no")); printf("Player blocks last field: %s\n", (level->block_last_field ? "yes" : "no")); printf("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no")); + printf("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no")); + printf("use step counter: %s\n", (level->use_step_counter ? "yes" : "no")); printf_line("-", 79); } @@ -2697,6 +3053,8 @@ static void setTapeInfoToDefaults() tape.recording = FALSE; tape.playing = FALSE; tape.pausing = FALSE; + + tape.no_valid_file = FALSE; } static int LoadTape_VERS(FILE *file, int chunk_size, struct TapeInfo *tape) @@ -2861,7 +3219,15 @@ void LoadTapeFromFilename(char *filename) setTapeInfoToDefaults(); if (!(file = fopen(filename, MODE_READ))) + { + tape.no_valid_file = TRUE; + +#if 0 + Error(ERR_WARN, "cannot read tape '%s' -- using empty tape", filename); +#endif + return; + } getFileChunkBE(file, chunk_name, NULL); if (strcmp(chunk_name, "RND1") == 0) @@ -2871,6 +3237,8 @@ void LoadTapeFromFilename(char *filename) getFileChunkBE(file, chunk_name, NULL); if (strcmp(chunk_name, "TAPE") != 0) { + tape.no_valid_file = TRUE; + Error(ERR_WARN, "unknown format of tape file '%s'", filename); fclose(file); return; @@ -2885,6 +3253,8 @@ void LoadTapeFromFilename(char *filename) if (!checkCookieString(cookie, TAPE_COOKIE_TMPL)) { + tape.no_valid_file = TRUE; + Error(ERR_WARN, "unknown format of tape file '%s'", filename); fclose(file); return; @@ -2892,6 +3262,8 @@ void LoadTapeFromFilename(char *filename) if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1) { + tape.no_valid_file = TRUE; + Error(ERR_WARN, "unsupported version of tape file '%s'", filename); fclose(file); return; @@ -3108,11 +3480,21 @@ void DumpTape(struct TapeInfo *tape) { int i, j; +#if 1 + if (tape->no_valid_file) + { + Error(ERR_WARN, "cannot dump -- no valid tape file found"); + + return; + } +#else if (TAPE_IS_EMPTY(*tape)) { Error(ERR_WARN, "no tape available for level %d", tape->level_nr); + return; } +#endif printf_line("-", 79); printf("Tape of Level %03d (file version %08d, game version %08d)\n",