X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=11f4c1425624e2c3ad73e9590d5b7412b7ef83e3;hb=a3d3dca44696b194dbe3f49a97d8c92d92b42ecf;hp=bffb16052eab5ce67593c85d8296d430d0bd57ed;hpb=d9479668b752772803e523561bc544a6c7efe725;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index bffb1605..451edb8b 100644 --- a/src/files.c +++ b/src/files.c @@ -19,9 +19,11 @@ #include "files.h" #include "init.h" #include "screens.h" +#include "editor.h" #include "tools.h" #include "tape.h" #include "config.h" +#include "api.h" #define ENABLE_UNUSED_CODE 0 // currently unused functions #define ENABLE_HISTORIC_CHUNKS 0 // only for historic reference @@ -51,6 +53,7 @@ // (element number only) #define LEVEL_CHUNK_GRPX_UNCHANGED 2 +#define LEVEL_CHUNK_EMPX_UNCHANGED 2 #define LEVEL_CHUNK_NOTE_UNCHANGED 2 // (nothing at all if unchanged) @@ -58,7 +61,6 @@ #define TAPE_CHUNK_VERS_SIZE 8 // size of file version chunk #define TAPE_CHUNK_HEAD_SIZE 20 // size of tape file header -#define TAPE_CHUNK_HEAD_UNUSED 1 // unused tape header bytes #define TAPE_CHUNK_SCRN_SIZE 2 // size of screen size chunk #define SCORE_CHUNK_VERS_SIZE 8 // size of file version chunk @@ -113,7 +115,7 @@ CONF_CONTENT_NUM_BYTES : 1) #define CONF_ELEMENT_BYTE_POS(i) ((i) * CONF_ELEMENT_NUM_BYTES) -#define CONF_ELEMENTS_ELEMENT(b,i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ +#define CONF_ELEMENTS_ELEMENT(b, i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ (b[CONF_ELEMENT_BYTE_POS(i) + 1])) #define CONF_CONTENT_ELEMENT_POS(c,x,y) ((c) * CONF_CONTENT_NUM_ELEMENTS + \ @@ -164,7 +166,6 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = TYPE_INTEGER, CONF_VALUE_8_BIT(1), &li.game_engine_type, GAME_ENGINE_TYPE_RND }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(1), @@ -175,102 +176,176 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = TYPE_INTEGER, CONF_VALUE_16_BIT(2), &li.fieldy, STD_LEV_FIELDY }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(3), &li.time, 100 }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(4), &li.gems_needed, 0 }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_32_BIT(2), &li.random_seed, 0 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), &li.use_step_counter, FALSE }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_8_BIT(4), &li.wind_direction_initial, MV_NONE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), &li.em_slippery_gems, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), &li.use_custom_template, FALSE }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_32_BIT(1), &li.can_move_into_acid_bits, ~0 // default: everything can }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_8_BIT(7), &li.dont_collide_with_bits, ~0 // default: always deadly }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(8), &li.em_explodes_by_fire, FALSE }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(5), &li.score[SC_TIME_BONUS], 1 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), &li.auto_exit_sokoban, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(10), &li.auto_count_gems, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(11), &li.solved_by_one_player, FALSE }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_8_BIT(12), &li.time_score_base, 1 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(13), &li.rate_time_over_score, FALSE }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(14), + &li.bd_intermission, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(15), + &li.bd_scheduling_type, GD_SCHEDULING_MILLISECONDS + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), + &li.bd_pal_timing, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(6), + &li.bd_cycle_delay_ms, 200 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(17), + &li.bd_cycle_delay_c64, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(18), + &li.bd_hatching_delay_cycles, 21 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(19), + &li.bd_hatching_delay_seconds, 2 + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(20), + &li.bd_line_shifting_borders, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(21), + &li.bd_scan_first_and_last_row, TRUE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(22), + &li.bd_short_explosions, TRUE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(23), + &li.bd_cave_random_seed_c64, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(3), + &li.bd_color_b, GD_C64_COLOR(0) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(4), + &li.bd_color_0, GD_C64_COLOR(0) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(5), + &li.bd_color_1, GD_C64_COLOR(8) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(6), + &li.bd_color_2, GD_C64_COLOR(11) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(7), + &li.bd_color_3, GD_C64_COLOR(1) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(8), + &li.bd_color_4, GD_C64_COLOR(5) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(9), + &li.bd_color_5, GD_C64_COLOR(6) + }, { -1, -1, @@ -562,771 +637,892 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = &li.initial_inventory_size[3], 1, MAX_INITIAL_INVENTORY_SIZE }, + // (these values are only valid for BD style levels) + // (some values for BD style amoeba following below) { - EL_EMERALD, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_EMERALD], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_diagonal_movements, FALSE }, - { - EL_DIAMOND, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_DIAMOND], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_topmost_player_active, TRUE }, - { - EL_BUG, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_BUG], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_pushing_prob, 25 }, - { - EL_SPACESHIP, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_SPACESHIP], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.bd_pushing_prob_with_sweet, 100 }, - { - EL_PACMAN, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_PACMAN], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), + &li.bd_push_mega_rock_with_sweet, FALSE }, - { - EL_NUT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_NUT], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(6), + &li.bd_snap_element, EL_EMPTY }, { - EL_DYNAMITE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_DYNAMITE], 10 + EL_BDX_SAND_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_sand_looks_like, EL_BDX_SAND_1 }, { - EL_KEY_1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_KEY], 10 + EL_BDX_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_rock_turns_to_on_falling, EL_BDX_ROCK_FALLING }, - { - EL_PEARL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_PEARL], 10 + EL_BDX_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_rock_turns_to_on_impact, EL_BDX_ROCK }, { - EL_CRYSTAL, -1, + EL_BDX_DIAMOND, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_CRYSTAL], 10 + &li.score[SC_DIAMOND_EXTRA], 20 + }, + { + EL_BDX_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_diamond_turns_to_on_falling, EL_BDX_DIAMOND_FALLING + }, + { + EL_BDX_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_diamond_turns_to_on_impact, EL_BDX_DIAMOND }, { - EL_BD_AMOEBA, -1, + EL_BDX_FIREFLY_1, -1, TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &li.amoeba_content, EL_DIAMOND + &li.bd_firefly_1_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.amoeba_speed, 10 + EL_BDX_FIREFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_firefly_2_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.grow_into_diggable, TRUE + EL_BDX_BUTTERFLY_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_1_explodes_to, EL_BDX_DIAMOND_GROWING_1 }, { - EL_YAMYAM, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &li.yamyam_content, EL_ROCK, NULL, - &li.num_yamyam_contents, 4, MAX_ELEMENT_CONTENTS + EL_BDX_BUTTERFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_2_explodes_to, EL_BDX_DIAMOND_GROWING_1 }, + { - EL_YAMYAM, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_YAMYAM], 10 + EL_BDX_STONEFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_stonefly_explodes_to, EL_BDX_ROCK_GROWING_1 }, { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_ROBOT], 10 + EL_BDX_DRAGONFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_dragonfly_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.slurp_score, 10 + EL_BDX_DIAMOND_GROWING_5, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_diamond_birth_turns_to, EL_BDX_DIAMOND }, { - EL_ROBOT_WHEEL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_wheel, 10 + EL_BDX_BOMB_EXPLODING_4, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bomb_explosion_turns_to, EL_BDX_WALL }, { - EL_MAGIC_WALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_magic_wall, 10 + EL_BDX_NITRO_PACK_EXPLODING_4, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_nitro_explosion_turns_to, EL_EMPTY }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &li.game_of_life[0], 2 + EL_BDX_EXPLODING_5, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_explosion_turns_to, EL_EMPTY }, + { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &li.game_of_life[1], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_magic_wall_wait_hatching, FALSE }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(3), - &li.game_of_life[2], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_magic_wall_stops_amoeba, TRUE }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(4), - &li.game_of_life[3], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_magic_wall_zero_infinite, TRUE }, { - EL_GAME_OF_LIFE, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), - &li.use_life_bugs, FALSE + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_magic_wall_break_scan, FALSE }, - { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &li.biomaze[0], 2 + EL_BDX_MAGIC_WALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_magic_wall_time, 999 }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &li.biomaze[1], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_magic_wall_diamond_to, EL_BDX_ROCK_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(3), - &li.biomaze[2], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_magic_wall_rock_to, EL_BDX_DIAMOND_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(4), - &li.biomaze[3], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_magic_wall_mega_rock_to, EL_BDX_NITRO_PACK_FALLING }, - { - EL_TIMEGATE_SWITCH, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_timegate, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_magic_wall_nut_to, EL_BDX_NUT_FALLING }, - { - EL_LIGHT_SWITCH_ACTIVE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_light, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_magic_wall_nitro_pack_to, EL_BDX_MEGA_ROCK_FALLING }, - { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.shield_normal_time, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &li.bd_magic_wall_flying_diamond_to, EL_BDX_FLYING_ROCK_FLYING }, { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(8), + &li.bd_magic_wall_flying_rock_to, EL_BDX_FLYING_DIAMOND_FLYING }, { - EL_SHIELD_DEADLY, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.shield_deadly_time, 10 + EL_BDX_CLOCK, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_clock_extra_time, 30 }, + { - EL_SHIELD_DEADLY, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_voodoo_collects_diamonds, FALSE }, - { - EL_EXTRA_TIME, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.extra_time, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_voodoo_hurt_kills_player, FALSE }, { - EL_EXTRA_TIME, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.extra_time_score, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_voodoo_dies_by_rock, FALSE }, - { - EL_TIME_ORB_FULL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_orb_time, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_voodoo_vanish_by_explosion, TRUE }, { - EL_TIME_ORB_FULL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_time_orb_bug, FALSE + EL_BDX_VOODOO_DOLL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &li.bd_voodoo_penalty_time, 30 }, { - EL_SPRING, -1, + EL_BDX_SLIME, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_spring_bug, FALSE + &li.bd_slime_is_predictable, TRUE }, - { - EL_EMC_ANDROID, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.android_move_time, 10 + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_slime_permeability_rate, 100 }, { - EL_EMC_ANDROID, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.android_clone_time, 10 + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_slime_permeability_bits_c64, 0 }, { - EL_EMC_ANDROID, SAVE_CONF_NEVER, - TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), - &li.android_clone_element[0], EL_EMPTY, NULL, - &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS_OLD + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(1), + &li.bd_slime_random_seed_c64, -1 }, { - EL_EMC_ANDROID, -1, - TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), - &li.android_clone_element[0], EL_EMPTY, NULL, - &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_slime_eats_element_1, EL_BDX_DIAMOND }, - { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.lenses_score, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_slime_converts_to_element_1, EL_BDX_DIAMOND_FALLING }, { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.lenses_time, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_slime_eats_element_2, EL_BDX_ROCK }, - { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.magnify_score, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_slime_converts_to_element_2, EL_BDX_ROCK_FALLING }, { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.magnify_time, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_slime_eats_element_3, EL_BDX_NUT + }, + { + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_slime_converts_to_element_3, EL_BDX_NUT_FALLING }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.ball_time, 10 + EL_BDX_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_acid_eats_element, EL_BDX_SAND_1 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.ball_random, FALSE + EL_BDX_ACID, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_acid_spread_rate, 3 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.ball_active_initial, FALSE + EL_BDX_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_acid_turns_to_element, EL_BDX_EXPLODING_3 + }, + + { + EL_BDX_BITER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_biter_move_delay, 0 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &li.ball_content, EL_EMPTY, NULL, - &li.num_ball_contents, 4, MAX_ELEMENT_CONTENTS + EL_BDX_BITER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_biter_eats_element, EL_BDX_DIAMOND }, { - EL_SOKOBAN_FIELD_EMPTY, -1, + EL_BDX_BLADDER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bladder_converts_by_element, EL_BDX_VOODOO_DOLL + }, + + { + EL_BDX_EXPANDABLE_WALL_ANY, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.sb_fields_needed, TRUE + &li.bd_change_expanding_wall, FALSE + }, + { + EL_BDX_EXPANDABLE_WALL_ANY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_expanding_wall_looks_like, EL_BDX_WALL }, { - EL_SOKOBAN_OBJECT, -1, + EL_BDX_REPLICATOR, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.sb_objects_needed, TRUE + &li.bd_replicators_active, TRUE + }, + { + EL_BDX_REPLICATOR, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_replicator_create_delay, 4 }, { - EL_MM_MCDUFFIN, -1, + EL_BDX_CONVEYOR_LEFT, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.mm_laser_red, FALSE + &li.bd_conveyor_belts_active, TRUE }, { - EL_MM_MCDUFFIN, -1, + EL_BDX_CONVEYOR_LEFT, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.mm_laser_green, FALSE + &li.bd_conveyor_belts_changed, FALSE }, + { - EL_MM_MCDUFFIN, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &li.mm_laser_blue, TRUE + EL_BDX_WATER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_water_cannot_flow_down, FALSE }, { - EL_DF_LASER, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.df_laser_red, TRUE + EL_BDX_NUT, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_nut_content, EL_BDX_NUT_BREAKING_1 + }, + + { + EL_BDX_PNEUMATIC_HAMMER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_hammer_walls_break_delay, 5 }, { - EL_DF_LASER, -1, + EL_BDX_PNEUMATIC_HAMMER, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.df_laser_green, TRUE + &li.bd_hammer_walls_reappear, FALSE }, { - EL_DF_LASER, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &li.df_laser_blue, FALSE + EL_BDX_PNEUMATIC_HAMMER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_hammer_walls_reappear_delay, 100 }, { - EL_MM_FUSE_ACTIVE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_fuse, 25 + EL_BDX_ROCKET_LAUNCHER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_infinite_rockets, FALSE }, + { - EL_MM_BOMB, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_bomb, 75 + EL_BDX_SKELETON, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_num_skeletons_needed_for_pot, 5 }, { - EL_MM_GRAY_BALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_ball, 75 + EL_BDX_SKELETON, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_skeleton_worth_num_diamonds, 0 }, + { - EL_MM_STEEL_BLOCK, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_block, 75 + EL_BDX_CREATURE_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_creatures_start_backwards, FALSE }, { - EL_MM_LIGHTBALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_ELEM_BONUS], 10 + EL_BDX_CREATURE_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_creatures_turn_on_hatching, FALSE }, - - // ---------- unused values ------------------------------------------------- - { - EL_UNKNOWN, SAVE_CONF_NEVER, + EL_BDX_CREATURE_SWITCH, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_UNKNOWN_15], 10 + &li.bd_creatures_auto_turn_delay, 0 }, { - -1, -1, - -1, -1, - NULL, -1 - } -}; - -static struct LevelFileConfigInfo chunk_config_NOTE[] = -{ - { - -1, -1, + EL_BDX_GRAVITY_SWITCH, -1, TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &xx_envelope.xsize, MAX_ENVELOPE_XSIZE, + &li.bd_gravity_direction, GD_MV_DOWN }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &xx_envelope.ysize, MAX_ENVELOPE_YSIZE, + EL_BDX_GRAVITY_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_gravity_switch_active, FALSE }, - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &xx_envelope.autowrap, FALSE + EL_BDX_GRAVITY_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_gravity_switch_delay, 10 }, { - -1, -1, + EL_BDX_GRAVITY_SWITCH, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), - &xx_envelope.centered, FALSE + &li.bd_gravity_affects_all, TRUE }, + // (the following values are related to various game elements) + { - -1, -1, - TYPE_STRING, CONF_VALUE_BYTES(1), - &xx_envelope.text, -1, NULL, - &xx_string_length_unused, -1, MAX_ENVELOPE_TEXT_LEN, - &xx_default_string_empty[0] + EL_EMERALD, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_EMERALD], 10 }, { - -1, -1, - -1, -1, - NULL, -1 - } -}; + EL_DIAMOND, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_DIAMOND], 10 + }, -static struct LevelFileConfigInfo chunk_config_CUSX_base[] = -{ { - -1, -1, - TYPE_STRING, CONF_VALUE_BYTES(1), - &xx_ei.description[0], -1, - &yy_ei.description[0], - &xx_string_length_unused, -1, MAX_ELEMENT_NAME_LEN, - &xx_default_description[0] + EL_BUG, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_BUG], 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(1), - &xx_ei.properties[EP_BITFIELD_BASE_NR], EP_BITMASK_BASE_DEFAULT, - &yy_ei.properties[EP_BITFIELD_BASE_NR] + EL_SPACESHIP, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_SPACESHIP], 10 }, -#if ENABLE_RESERVED_CODE - // (reserved for later use) + { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(2), - &xx_ei.properties[EP_BITFIELD_BASE_NR + 1], EP_BITMASK_DEFAULT, - &yy_ei.properties[EP_BITFIELD_BASE_NR + 1] + EL_PACMAN, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_PACMAN], 10 }, -#endif { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &xx_ei.use_gfx_element, FALSE, - &yy_ei.use_gfx_element + EL_NUT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_NUT], 10 }, + { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_ei.gfx_element_initial, EL_EMPTY_SPACE, - &yy_ei.gfx_element_initial + EL_DYNAMITE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_DYNAMITE], 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(2), - &xx_ei.access_direction, MV_ALL_DIRECTIONS, - &yy_ei.access_direction + EL_KEY_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_KEY], 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &xx_ei.collect_score_initial, 10, - &yy_ei.collect_score_initial + EL_PEARL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_PEARL], 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(3), - &xx_ei.collect_count_initial, 1, - &yy_ei.collect_count_initial + EL_CRYSTAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_CRYSTAL], 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(4), - &xx_ei.ce_value_fixed_initial, 0, - &yy_ei.ce_value_fixed_initial + EL_BD_AMOEBA, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.amoeba_content, EL_DIAMOND }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(5), - &xx_ei.ce_value_random_initial, 0, - &yy_ei.ce_value_random_initial + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.amoeba_speed, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &xx_ei.use_last_ce_value, FALSE, - &yy_ei.use_last_ce_value + EL_BD_AMOEBA, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.grow_into_diggable, TRUE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(6), - &xx_ei.push_delay_fixed, 8, - &yy_ei.push_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_amoeba_1_threshold_too_big, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(7), - &xx_ei.push_delay_random, 8, - &yy_ei.push_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.bd_amoeba_1_slow_growth_time, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(8), - &xx_ei.drop_delay_fixed, 0, - &yy_ei.drop_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_1_content_too_big, EL_BDX_ROCK }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(9), - &xx_ei.drop_delay_random, 0, - &yy_ei.drop_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_1_content_enclosed, EL_BDX_DIAMOND }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(10), - &xx_ei.move_delay_fixed, 0, - &yy_ei.move_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_amoeba_1_slow_growth_rate, 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(11), - &xx_ei.move_delay_random, 0, - &yy_ei.move_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_amoeba_1_fast_growth_rate, 25 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(16), - &xx_ei.step_delay_fixed, 0, - &yy_ei.step_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_amoeba_wait_for_hatching, FALSE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(17), - &xx_ei.step_delay_random, 0, - &yy_ei.step_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_amoeba_start_immediately, TRUE }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_ei.move_pattern, MV_ALL_DIRECTIONS, - &yy_ei.move_pattern + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_amoeba_2_threshold_too_big, 200 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(4), - &xx_ei.move_direction_initial, MV_START_AUTOMATIC, - &yy_ei.move_direction_initial + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.bd_amoeba_2_slow_growth_time, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(5), - &xx_ei.move_stepsize, TILEX / 8, - &yy_ei.move_stepsize + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_2_content_too_big, EL_BDX_ROCK }, - { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(12), - &xx_ei.move_enter_element, EL_EMPTY_SPACE, - &yy_ei.move_enter_element + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_2_content_enclosed, EL_BDX_DIAMOND }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(13), - &xx_ei.move_leave_element, EL_EMPTY_SPACE, - &yy_ei.move_leave_element + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_amoeba_2_content_exploding, EL_EMPTY }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(6), - &xx_ei.move_leave_type, LEAVE_TYPE_UNLIMITED, - &yy_ei.move_leave_type + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_amoeba_2_content_looks_like, EL_BDX_AMOEBA_2 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(7), - &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, - &yy_ei.slippery_type + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_amoeba_2_slow_growth_rate, 3 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(8), - &xx_ei.explosion_type, EXPLODES_3X3, - &yy_ei.explosion_type + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_amoeba_2_fast_growth_rate, 25 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(14), - &xx_ei.explosion_delay, 16, - &yy_ei.explosion_delay + EL_BDX_AMOEBA_2, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_amoeba_2_explode_by_amoeba, TRUE }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(15), - &xx_ei.ignition_delay, 8, - &yy_ei.ignition_delay + EL_YAMYAM, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &li.yamyam_content, EL_ROCK, NULL, + &li.num_yamyam_contents, 4, MAX_ELEMENT_CONTENTS }, - { - -1, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(2), - &xx_ei.content, EL_EMPTY_SPACE, - &yy_ei.content, - &xx_num_contents, 1, 1 + EL_YAMYAM, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_YAMYAM], 10 }, - // ---------- "num_change_pages" must be the last entry --------------------- - { - -1, SAVE_CONF_ALWAYS, - TYPE_INTEGER, CONF_VALUE_8_BIT(9), - &xx_ei.num_change_pages, 1, - &yy_ei.num_change_pages + EL_ROBOT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_ROBOT], 10 }, - { - -1, -1, - -1, -1, - NULL, -1, - NULL - } -}; - -static struct LevelFileConfigInfo chunk_config_CUSX_change[] = -{ - // ---------- "current_change_page" must be the first entry ----------------- + EL_ROBOT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.slurp_score, 10 + }, { - -1, SAVE_CONF_ALWAYS, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &xx_current_change_page, -1 + EL_ROBOT_WHEEL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_wheel, 10 }, - // ---------- (the remaining entries can be in any order) ------------------- - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &xx_change.can_change, FALSE + EL_MAGIC_WALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_magic_wall, 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(1), - &xx_event_bits[0], 0 + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.game_of_life[0], 2 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(2), - &xx_event_bits[1], 0 + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.game_of_life[1], 3 }, - { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(3), - &xx_change.trigger_player, CH_PLAYER_ANY + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.game_of_life[2], 3 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(4), - &xx_change.trigger_side, CH_SIDE_ANY + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.game_of_life[3], 3 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_change.trigger_page, CH_PAGE_ANY + EL_GAME_OF_LIFE, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), + &li.use_life_bugs, FALSE }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_change.target_element, EL_EMPTY_SPACE + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.biomaze[0], 2 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &xx_change.delay_fixed, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.biomaze[1], 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(3), - &xx_change.delay_random, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.biomaze[2], 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(4), - &xx_change.delay_frames, FRAMES_PER_SECOND + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.biomaze[3], 3 }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(5), - &xx_change.initial_trigger_element, EL_EMPTY_SPACE + EL_TIMEGATE_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_timegate, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), - &xx_change.explode, FALSE - }, - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(7), - &xx_change.use_target_content, FALSE + EL_LIGHT_SWITCH_ACTIVE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_light, 10 }, + { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(8), - &xx_change.only_if_complete, FALSE + EL_SHIELD_NORMAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.shield_normal_time, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), - &xx_change.use_random_replace, FALSE + EL_SHIELD_NORMAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.score[SC_SHIELD], 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(10), - &xx_change.random_percentage, 100 + EL_SHIELD_DEADLY, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.shield_deadly_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(11), - &xx_change.replace_when, CP_WHEN_EMPTY + EL_SHIELD_DEADLY, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.score[SC_SHIELD], 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(12), - &xx_change.has_action, FALSE + EL_EXTRA_TIME, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.extra_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(13), - &xx_change.action_type, CA_NO_ACTION + EL_EXTRA_TIME, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.extra_time_score, 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(14), - &xx_change.action_mode, CA_MODE_UNDEFINED + EL_TIME_ORB_FULL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_orb_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(6), - &xx_change.action_arg, CA_ARG_UNDEFINED + EL_TIME_ORB_FULL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.use_time_orb_bug, FALSE }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(7), - &xx_change.action_element, EL_EMPTY_SPACE + EL_SPRING, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.use_spring_bug, FALSE }, { - -1, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &xx_change.target_content, EL_EMPTY_SPACE, NULL, - &xx_num_contents, 1, 1 - }, + EL_EMC_ANDROID, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.android_move_time, 10 + }, + { + EL_EMC_ANDROID, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.android_clone_time, 10 + }, + { + EL_EMC_ANDROID, SAVE_CONF_NEVER, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), + &li.android_clone_element[0], EL_EMPTY, NULL, + &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS_OLD + }, + { + EL_EMC_ANDROID, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), + &li.android_clone_element[0], EL_EMPTY, NULL, + &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS + }, + + { + EL_EMC_LENSES, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.lenses_score, 10 + }, + { + EL_EMC_LENSES, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.lenses_time, 10 + }, + + { + EL_EMC_MAGNIFIER, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.magnify_score, 10 + }, + { + EL_EMC_MAGNIFIER, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.magnify_time, 10 + }, + + { + EL_EMC_MAGIC_BALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.ball_time, 10 + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.ball_random, FALSE + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.ball_active_initial, FALSE + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &li.ball_content, EL_EMPTY, NULL, + &li.num_ball_contents, 4, MAX_ELEMENT_CONTENTS + }, + + { + EL_SOKOBAN_FIELD_EMPTY, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.sb_fields_needed, TRUE + }, + + { + EL_SOKOBAN_OBJECT, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.sb_objects_needed, TRUE + }, + + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.mm_laser_red, FALSE + }, + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.mm_laser_green, FALSE + }, + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.mm_laser_blue, TRUE + }, + + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.df_laser_red, TRUE + }, + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.df_laser_green, TRUE + }, + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.df_laser_blue, FALSE + }, + + { + EL_MM_FUSE_ACTIVE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_fuse, 25 + }, + { + EL_MM_BOMB, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_bomb, 75 + }, + + { + EL_MM_GRAY_BALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_ball, 75 + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.mm_ball_choice_mode, ANIM_RANDOM + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), + &li.mm_ball_content, EL_EMPTY, NULL, + &li.num_mm_ball_contents, 8, MAX_MM_BALL_CONTENTS + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.rotate_mm_ball_content, TRUE + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.explode_mm_ball, FALSE + }, + + { + EL_MM_STEEL_BLOCK, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_block, 75 + }, + { + EL_MM_LIGHTBALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_ELEM_BONUS], 10 + }, { -1, -1, @@ -1335,38 +1531,36 @@ static struct LevelFileConfigInfo chunk_config_CUSX_change[] = } }; -static struct LevelFileConfigInfo chunk_config_GRPX[] = +static struct LevelFileConfigInfo chunk_config_NOTE[] = { { -1, -1, - TYPE_STRING, CONF_VALUE_BYTES(1), - &xx_ei.description[0], -1, NULL, - &xx_string_length_unused, -1, MAX_ELEMENT_NAME_LEN, - &xx_default_description[0] + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &xx_envelope.xsize, MAX_ENVELOPE_XSIZE, }, - { -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &xx_ei.use_gfx_element, FALSE + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &xx_envelope.ysize, MAX_ENVELOPE_YSIZE, }, + { -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &xx_envelope.autowrap, FALSE }, - { -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &xx_group.choice_mode, ANIM_RANDOM + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &xx_envelope.centered, FALSE }, { -1, -1, - TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), - &xx_group.element[0], EL_EMPTY_SPACE, NULL, - &xx_group.num_elements, 1, MAX_ELEMENTS_IN_GROUP + TYPE_STRING, CONF_VALUE_BYTES(1), + &xx_envelope.text, -1, NULL, + &xx_string_length_unused, -1, MAX_ENVELOPE_TEXT_LEN, + &xx_default_string_empty[0] }, { @@ -1376,7302 +1570,7612 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] = } }; -static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE) +static struct LevelFileConfigInfo chunk_config_CUSX_base[] = { { - EL_PLAYER_1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), - &li.block_snap_field, TRUE + -1, -1, + TYPE_STRING, CONF_VALUE_BYTES(1), + &xx_ei.description[0], -1, + &yy_ei.description[0], + &xx_string_length_unused, -1, MAX_ELEMENT_NAME_LEN, + &xx_default_description[0] }, + { - EL_PLAYER_1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(13), - &li.continuous_snapping, TRUE + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(1), + &xx_ei.properties[EP_BITFIELD_BASE_NR], EP_BITMASK_BASE_DEFAULT, + &yy_ei.properties[EP_BITFIELD_BASE_NR] }, +#if ENABLE_RESERVED_CODE + // (reserved for later use) { - EL_PLAYER_1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &li.initial_player_stepsize[0], STEPSIZE_NORMAL + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(2), + &xx_ei.properties[EP_BITFIELD_BASE_NR + 1], EP_BITMASK_DEFAULT, + &yy_ei.properties[EP_BITFIELD_BASE_NR + 1] }, +#endif + { - EL_PLAYER_1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(10), - &li.use_start_element[0], FALSE + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE, + &yy_ei.use_gfx_element }, { - EL_PLAYER_1, -1, + -1, -1, TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &li.start_element[0], EL_PLAYER_1 - }, - { - EL_PLAYER_1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(11), - &li.use_artwork_element[0], FALSE + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE, + &yy_ei.gfx_element_initial }, + { - EL_PLAYER_1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(2), - &li.artwork_element[0], EL_PLAYER_1 + -1, -1, + TYPE_BITFIELD, CONF_VALUE_8_BIT(2), + &xx_ei.access_direction, MV_ALL_DIRECTIONS, + &yy_ei.access_direction }, + { - EL_PLAYER_1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(12), - &li.use_explosion_element[0], FALSE + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &xx_ei.collect_score_initial, 10, + &yy_ei.collect_score_initial }, { - EL_PLAYER_1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(3), - &li.explosion_element[0], EL_PLAYER_1 + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(3), + &xx_ei.collect_count_initial, 1, + &yy_ei.collect_count_initial }, { -1, -1, - -1, -1, - NULL, -1 - } -}; - -static struct -{ - int filetype; - char *id; -} -filetype_id_list[] = -{ - { LEVEL_FILE_TYPE_RND, "RND" }, - { LEVEL_FILE_TYPE_BD, "BD" }, - { LEVEL_FILE_TYPE_EM, "EM" }, - { LEVEL_FILE_TYPE_SP, "SP" }, - { LEVEL_FILE_TYPE_DX, "DX" }, - { LEVEL_FILE_TYPE_SB, "SB" }, - { LEVEL_FILE_TYPE_DC, "DC" }, - { LEVEL_FILE_TYPE_MM, "MM" }, - { LEVEL_FILE_TYPE_MM, "DF" }, - { -1, NULL }, -}; - - -// ============================================================================ -// level file functions -// ============================================================================ - -static boolean check_special_flags(char *flag) -{ - if (strEqual(options.special_flags, flag) || - strEqual(leveldir_current->special_flags, flag)) - return TRUE; - - return FALSE; -} - -static struct DateInfo getCurrentDate(void) -{ - time_t epoch_seconds = time(NULL); - struct tm *now = localtime(&epoch_seconds); - struct DateInfo date; - - date.year = now->tm_year + 1900; - date.month = now->tm_mon + 1; - date.day = now->tm_mday; - - date.src = DATE_SRC_CLOCK; - - return date; -} - -static void resetEventFlags(struct ElementChangeInfo *change) -{ - int i; - - for (i = 0; i < NUM_CHANGE_EVENTS; i++) - change->has_event[i] = FALSE; -} - -static void resetEventBits(void) -{ - int i; - - for (i = 0; i < NUM_CE_BITFIELDS; i++) - xx_event_bits[i] = 0; -} - -static void setEventFlagsFromEventBits(struct ElementChangeInfo *change) -{ - int i; - - /* important: only change event flag if corresponding event bit is set - (this is because all xx_event_bits[] values are loaded separately, - and all xx_event_bits[] values are set back to zero before loading - another value xx_event_bits[x] (each value representing 32 flags)) */ - - for (i = 0; i < NUM_CHANGE_EVENTS; i++) - if (xx_event_bits[CH_EVENT_BITFIELD_NR(i)] & CH_EVENT_BIT(i)) - change->has_event[i] = TRUE; -} - -static void setEventBitsFromEventFlags(struct ElementChangeInfo *change) -{ - int i; - - /* in contrast to the above function setEventFlagsFromEventBits(), it - would also be possible to set all bits in xx_event_bits[] to 0 or 1 - depending on the corresponding change->has_event[i] values here, as - all xx_event_bits[] values are reset in resetEventBits() before */ - - for (i = 0; i < NUM_CHANGE_EVENTS; i++) - if (change->has_event[i]) - xx_event_bits[CH_EVENT_BITFIELD_NR(i)] |= CH_EVENT_BIT(i); -} - -static char *getDefaultElementDescription(struct ElementInfo *ei) -{ - static char description[MAX_ELEMENT_NAME_LEN + 1]; - char *default_description = (ei->custom_description != NULL ? - ei->custom_description : - ei->editor_description); - int i; - - // always start with reliable default values - for (i = 0; i < MAX_ELEMENT_NAME_LEN + 1; i++) - description[i] = '\0'; - - // truncate element description to MAX_ELEMENT_NAME_LEN bytes - strncpy(description, default_description, MAX_ELEMENT_NAME_LEN); - - return &description[0]; -} - -static void setElementDescriptionToDefault(struct ElementInfo *ei) -{ - char *default_description = getDefaultElementDescription(ei); - int i; - - for (i = 0; i < MAX_ELEMENT_NAME_LEN + 1; i++) - ei->description[i] = default_description[i]; -} - -static void setConfigToDefaultsFromConfigList(struct LevelFileConfigInfo *conf) -{ - int i; - - for (i = 0; conf[i].data_type != -1; i++) - { - int default_value = conf[i].default_value; - int data_type = conf[i].data_type; - int conf_type = conf[i].conf_type; - int byte_mask = conf_type & CONF_MASK_BYTES; - - if (byte_mask == CONF_MASK_MULTI_BYTES) - { - int default_num_entities = conf[i].default_num_entities; - int max_num_entities = conf[i].max_num_entities; - - *(int *)(conf[i].num_entities) = default_num_entities; - - if (data_type == TYPE_STRING) - { - char *default_string = conf[i].default_string; - char *string = (char *)(conf[i].value); - - strncpy(string, default_string, max_num_entities); - } - else if (data_type == TYPE_ELEMENT_LIST) - { - int *element_array = (int *)(conf[i].value); - int j; - - for (j = 0; j < max_num_entities; j++) - element_array[j] = default_value; - } - else if (data_type == TYPE_CONTENT_LIST) - { - struct Content *content = (struct Content *)(conf[i].value); - int c, x, y; - - for (c = 0; c < max_num_entities; c++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content[c].e[x][y] = default_value; - } - } - else // constant size configuration data (1, 2 or 4 bytes) - { - if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value) = default_value; - else - *(int *) (conf[i].value) = default_value; - } - } -} - -static void copyConfigFromConfigList(struct LevelFileConfigInfo *conf) -{ - int i; - - for (i = 0; conf[i].data_type != -1; i++) - { - int data_type = conf[i].data_type; - int conf_type = conf[i].conf_type; - int byte_mask = conf_type & CONF_MASK_BYTES; - - if (byte_mask == CONF_MASK_MULTI_BYTES) - { - int max_num_entities = conf[i].max_num_entities; - - if (data_type == TYPE_STRING) - { - char *string = (char *)(conf[i].value); - char *string_copy = (char *)(conf[i].value_copy); - - strncpy(string_copy, string, max_num_entities); - } - else if (data_type == TYPE_ELEMENT_LIST) - { - int *element_array = (int *)(conf[i].value); - int *element_array_copy = (int *)(conf[i].value_copy); - int j; - - for (j = 0; j < max_num_entities; j++) - element_array_copy[j] = element_array[j]; - } - else if (data_type == TYPE_CONTENT_LIST) - { - struct Content *content = (struct Content *)(conf[i].value); - struct Content *content_copy = (struct Content *)(conf[i].value_copy); - int c, x, y; - - for (c = 0; c < max_num_entities; c++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content_copy[c].e[x][y] = content[c].e[x][y]; - } - } - else // constant size configuration data (1, 2 or 4 bytes) - { - if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value_copy) = *(boolean *)(conf[i].value); - else - *(int *) (conf[i].value_copy) = *(int *) (conf[i].value); - } - } -} - -void copyElementInfo(struct ElementInfo *ei_from, struct ElementInfo *ei_to) -{ - int i; - - xx_ei = *ei_from; // copy element data into temporary buffer - yy_ei = *ei_to; // copy element data into temporary buffer - - copyConfigFromConfigList(chunk_config_CUSX_base); - - *ei_from = xx_ei; - *ei_to = yy_ei; - - // ---------- reinitialize and copy change pages ---------- - - ei_to->num_change_pages = ei_from->num_change_pages; - ei_to->current_change_page = ei_from->current_change_page; - - setElementChangePages(ei_to, ei_to->num_change_pages); - - for (i = 0; i < ei_to->num_change_pages; i++) - ei_to->change_page[i] = ei_from->change_page[i]; - - // ---------- copy group element info ---------- - if (ei_from->group != NULL && ei_to->group != NULL) // group or internal - *ei_to->group = *ei_from->group; - - // mark this custom element as modified - ei_to->modified_settings = TRUE; -} - -void setElementChangePages(struct ElementInfo *ei, int change_pages) -{ - int change_page_size = sizeof(struct ElementChangeInfo); - - ei->num_change_pages = MAX(1, change_pages); - - ei->change_page = - checked_realloc(ei->change_page, ei->num_change_pages * change_page_size); - - if (ei->current_change_page >= ei->num_change_pages) - ei->current_change_page = ei->num_change_pages - 1; - - ei->change = &ei->change_page[ei->current_change_page]; -} - -void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) -{ - xx_change = *change; // copy change data into temporary buffer - - setConfigToDefaultsFromConfigList(chunk_config_CUSX_change); - - *change = xx_change; - - resetEventFlags(change); - - change->direct_action = 0; - change->other_action = 0; - - change->pre_change_function = NULL; - change->change_function = NULL; - change->post_change_function = NULL; -} - -static void setLevelInfoToDefaults_Level(struct LevelInfo *level) -{ - int i, x, y; - - li = *level; // copy level data into temporary buffer - setConfigToDefaultsFromConfigList(chunk_config_INFO); - *level = li; // copy temporary buffer back to level data - - setLevelInfoToDefaults_EM(); - setLevelInfoToDefaults_SP(); - setLevelInfoToDefaults_MM(); - - level->native_em_level = &native_em_level; - level->native_sp_level = &native_sp_level; - level->native_mm_level = &native_mm_level; - - level->file_version = FILE_VERSION_ACTUAL; - level->game_version = GAME_VERSION_ACTUAL; - - level->creation_date = getCurrentDate(); - - level->encoding_16bit_field = TRUE; - level->encoding_16bit_yamyam = TRUE; - level->encoding_16bit_amoeba = TRUE; - - // clear level name and level author string buffers - for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) - level->name[i] = '\0'; - for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) - level->author[i] = '\0'; - - // set level name and level author to default values - strcpy(level->name, NAMELESS_LEVEL_NAME); - strcpy(level->author, ANONYMOUS_NAME); - - // set level playfield to playable default level with player and exit - for (x = 0; x < MAX_LEV_FIELDX; x++) - for (y = 0; y < MAX_LEV_FIELDY; y++) - level->field[x][y] = EL_SAND; - - level->field[0][0] = EL_PLAYER_1; - level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED; - - BorderElement = EL_STEELWALL; - - // detect custom elements when loading them - level->file_has_custom_elements = FALSE; - - // set all bug compatibility flags to "false" => do not emulate this bug - level->use_action_after_change_bug = FALSE; - - if (leveldir_current) - { - // try to determine better author name than 'anonymous' - if (!strEqual(leveldir_current->author, ANONYMOUS_NAME)) - { - strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN); - level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; - } - else - { - switch (LEVELCLASS(leveldir_current)) - { - case LEVELCLASS_TUTORIAL: - strcpy(level->author, PROGRAM_AUTHOR_STRING); - break; - - case LEVELCLASS_CONTRIB: - strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN); - level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; - break; - - case LEVELCLASS_PRIVATE: - strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN); - level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; - break; - - default: - // keep default value - break; - } - } - } -} - -static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) -{ - static boolean clipboard_elements_initialized = FALSE; - int i; - - InitElementPropertiesStatic(); - - li = *level; // copy level data into temporary buffer - setConfigToDefaultsFromConfigList(chunk_config_ELEM); - *level = li; // copy temporary buffer back to level data - - for (i = 0; i < MAX_NUM_ELEMENTS; i++) - { - int element = i; - struct ElementInfo *ei = &element_info[element]; - - // never initialize clipboard elements after the very first time - // (to be able to use clipboard elements between several levels) - if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized) - continue; - - if (IS_ENVELOPE(element)) - { - int envelope_nr = element - EL_ENVELOPE_1; - - setConfigToDefaultsFromConfigList(chunk_config_NOTE); - - level->envelope[envelope_nr] = xx_envelope; - } - - if (IS_CUSTOM_ELEMENT(element) || - IS_GROUP_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) - { - xx_ei = *ei; // copy element data into temporary buffer - - setConfigToDefaultsFromConfigList(chunk_config_CUSX_base); - - *ei = xx_ei; - } - - setElementChangePages(ei, 1); - setElementChangeInfoToDefaults(ei->change); - - if (IS_CUSTOM_ELEMENT(element) || - IS_GROUP_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) - { - setElementDescriptionToDefault(ei); - - ei->modified_settings = FALSE; - } - - if (IS_CUSTOM_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) - { - // internal values used in level editor - - ei->access_type = 0; - ei->access_layer = 0; - ei->access_protected = 0; - ei->walk_to_action = 0; - ei->smash_targets = 0; - ei->deadliness = 0; - - ei->can_explode_by_fire = FALSE; - ei->can_explode_smashed = FALSE; - ei->can_explode_impact = FALSE; - - ei->current_change_page = 0; - } - - if (IS_GROUP_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) - { - struct ElementGroupInfo *group; + TYPE_INTEGER, CONF_VALUE_16_BIT(4), + &xx_ei.ce_value_fixed_initial, 0, + &yy_ei.ce_value_fixed_initial + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(5), + &xx_ei.ce_value_random_initial, 0, + &yy_ei.ce_value_random_initial + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &xx_ei.use_last_ce_value, FALSE, + &yy_ei.use_last_ce_value + }, - // initialize memory for list of elements in group - if (ei->group == NULL) - ei->group = checked_malloc(sizeof(struct ElementGroupInfo)); + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(6), + &xx_ei.push_delay_fixed, 8, + &yy_ei.push_delay_fixed + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(7), + &xx_ei.push_delay_random, 8, + &yy_ei.push_delay_random + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(8), + &xx_ei.drop_delay_fixed, 0, + &yy_ei.drop_delay_fixed + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(9), + &xx_ei.drop_delay_random, 0, + &yy_ei.drop_delay_random + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(10), + &xx_ei.move_delay_fixed, 0, + &yy_ei.move_delay_fixed + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(11), + &xx_ei.move_delay_random, 0, + &yy_ei.move_delay_random + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(16), + &xx_ei.step_delay_fixed, 0, + &yy_ei.step_delay_fixed + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(17), + &xx_ei.step_delay_random, 0, + &yy_ei.step_delay_random + }, - group = ei->group; + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(3), + &xx_ei.move_pattern, MV_ALL_DIRECTIONS, + &yy_ei.move_pattern + }, + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_8_BIT(4), + &xx_ei.move_direction_initial, MV_START_AUTOMATIC, + &yy_ei.move_direction_initial + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &xx_ei.move_stepsize, TILEX / 8, + &yy_ei.move_stepsize + }, - xx_group = *group; // copy group data into temporary buffer + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(12), + &xx_ei.move_enter_element, EL_EMPTY_SPACE, + &yy_ei.move_enter_element + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(13), + &xx_ei.move_leave_element, EL_EMPTY_SPACE, + &yy_ei.move_leave_element + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(6), + &xx_ei.move_leave_type, LEAVE_TYPE_UNLIMITED, + &yy_ei.move_leave_type + }, - setConfigToDefaultsFromConfigList(chunk_config_GRPX); + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(7), + &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, + &yy_ei.slippery_type + }, - *group = xx_group; - } - } + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(8), + &xx_ei.explosion_type, EXPLODES_3X3, + &yy_ei.explosion_type + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(14), + &xx_ei.explosion_delay, 16, + &yy_ei.explosion_delay + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(15), + &xx_ei.ignition_delay, 8, + &yy_ei.ignition_delay + }, - clipboard_elements_initialized = TRUE; -} + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(2), + &xx_ei.content, EL_EMPTY_SPACE, + &yy_ei.content, + &xx_num_contents, 1, 1 + }, -static void setLevelInfoToDefaults(struct LevelInfo *level, - boolean level_info_only, - boolean reset_file_status) -{ - setLevelInfoToDefaults_Level(level); + // ---------- "num_change_pages" must be the last entry --------------------- - if (!level_info_only) - setLevelInfoToDefaults_Elements(level); + { + -1, SAVE_CONF_ALWAYS, + TYPE_INTEGER, CONF_VALUE_8_BIT(9), + &xx_ei.num_change_pages, 1, + &yy_ei.num_change_pages + }, - if (reset_file_status) { - level->no_valid_file = FALSE; - level->no_level_file = FALSE; + -1, -1, + -1, -1, + NULL, -1, + NULL } +}; - level->changed = FALSE; -} - -static void setFileInfoToDefaults(struct LevelFileInfo *level_file_info) -{ - level_file_info->nr = 0; - level_file_info->type = LEVEL_FILE_TYPE_UNKNOWN; - level_file_info->packed = FALSE; - - setString(&level_file_info->basename, NULL); - setString(&level_file_info->filename, NULL); -} - -int getMappedElement_SB(int, boolean); - -static void ActivateLevelTemplate(void) +static struct LevelFileConfigInfo chunk_config_CUSX_change[] = { - int x, y; + // ---------- "current_change_page" must be the first entry ----------------- - if (check_special_flags("load_xsb_to_ces")) { - // fill smaller playfields with padding "beyond border wall" elements - if (level.fieldx < level_template.fieldx || - level.fieldy < level_template.fieldy) - { - short field[level.fieldx][level.fieldy]; - int new_fieldx = MAX(level.fieldx, level_template.fieldx); - int new_fieldy = MAX(level.fieldy, level_template.fieldy); - int pos_fieldx = (new_fieldx - level.fieldx) / 2; - int pos_fieldy = (new_fieldy - level.fieldy) / 2; - - // copy old playfield (which is smaller than the visible area) - for (y = 0; y < level.fieldy; y++) for (x = 0; x < level.fieldx; x++) - field[x][y] = level.field[x][y]; - - // fill new, larger playfield with "beyond border wall" elements - for (y = 0; y < new_fieldy; y++) for (x = 0; x < new_fieldx; x++) - level.field[x][y] = getMappedElement_SB('_', TRUE); - - // copy the old playfield to the middle of the new playfield - for (y = 0; y < level.fieldy; y++) for (x = 0; x < level.fieldx; x++) - level.field[pos_fieldx + x][pos_fieldy + y] = field[x][y]; - - level.fieldx = new_fieldx; - level.fieldy = new_fieldy; - } - } - - // Currently there is no special action needed to activate the template - // data, because 'element_info' property settings overwrite the original - // level data, while all other variables do not change. - - // Exception: 'from_level_template' elements in the original level playfield - // are overwritten with the corresponding elements at the same position in - // playfield from the level template. - - for (x = 0; x < level.fieldx; x++) - for (y = 0; y < level.fieldy; y++) - if (level.field[x][y] == EL_FROM_LEVEL_TEMPLATE) - level.field[x][y] = level_template.field[x][y]; + -1, SAVE_CONF_ALWAYS, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &xx_current_change_page, -1 + }, + + // ---------- (the remaining entries can be in any order) ------------------- - if (check_special_flags("load_xsb_to_ces")) { - struct LevelInfo level_backup = level; + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &xx_change.can_change, FALSE + }, - // overwrite all individual level settings from template level settings - level = level_template; + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(1), + &xx_event_bits[0], 0 + }, + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(2), + &xx_event_bits[1], 0 + }, - // restore level file info - level.file_info = level_backup.file_info; + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_8_BIT(3), + &xx_change.trigger_player, CH_PLAYER_ANY + }, + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_8_BIT(4), + &xx_change.trigger_side, CH_SIDE_ANY + }, + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_32_BIT(3), + &xx_change.trigger_page, CH_PAGE_ANY + }, - // restore playfield size - level.fieldx = level_backup.fieldx; - level.fieldy = level_backup.fieldy; + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_change.target_element, EL_EMPTY_SPACE + }, - // restore playfield content - for (x = 0; x < level.fieldx; x++) - for (y = 0; y < level.fieldy; y++) - level.field[x][y] = level_backup.field[x][y]; + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &xx_change.delay_fixed, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(3), + &xx_change.delay_random, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(4), + &xx_change.delay_frames, FRAMES_PER_SECOND + }, - // restore name and author from individual level - strcpy(level.name, level_backup.name); - strcpy(level.author, level_backup.author); + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &xx_change.initial_trigger_element, EL_EMPTY_SPACE + }, - // restore flag "use_custom_template" - level.use_custom_template = level_backup.use_custom_template; - } -} + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), + &xx_change.explode, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(7), + &xx_change.use_target_content, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(8), + &xx_change.only_if_complete, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), + &xx_change.use_random_replace, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(10), + &xx_change.random_percentage, 100 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(11), + &xx_change.replace_when, CP_WHEN_EMPTY + }, -static char *getLevelFilenameFromBasename(char *basename) -{ - static char *filename = NULL; + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(12), + &xx_change.has_action, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(13), + &xx_change.action_type, CA_NO_ACTION + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(14), + &xx_change.action_mode, CA_MODE_UNDEFINED + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(6), + &xx_change.action_arg, CA_ARG_UNDEFINED + }, - checked_free(filename); + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &xx_change.action_element, EL_EMPTY_SPACE + }, - filename = getPath2(getCurrentLevelDir(), basename); + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &xx_change.target_content, EL_EMPTY_SPACE, NULL, + &xx_num_contents, 1, 1 + }, - return filename; -} + { + -1, -1, + -1, -1, + NULL, -1 + } +}; -static int getFileTypeFromBasename(char *basename) +static struct LevelFileConfigInfo chunk_config_GRPX[] = { - // !!! ALSO SEE COMMENT IN checkForPackageFromBasename() !!! + { + -1, -1, + TYPE_STRING, CONF_VALUE_BYTES(1), + &xx_ei.description[0], -1, NULL, + &xx_string_length_unused, -1, MAX_ELEMENT_NAME_LEN, + &xx_default_description[0] + }, - static char *filename = NULL; - struct stat file_status; + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + }, - // ---------- try to determine file type from filename ---------- + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &xx_group.choice_mode, ANIM_RANDOM + }, - // check for typical filename of a Supaplex level package file - if (strlen(basename) == 10 && strPrefixLower(basename, "levels.d")) - return LEVEL_FILE_TYPE_SP; + { + -1, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), + &xx_group.element[0], EL_EMPTY_SPACE, NULL, + &xx_group.num_elements, 1, MAX_ELEMENTS_IN_GROUP + }, - // check for typical filename of a Diamond Caves II level package file - if (strSuffixLower(basename, ".dc") || - strSuffixLower(basename, ".dc2")) - return LEVEL_FILE_TYPE_DC; + { + -1, -1, + -1, -1, + NULL, -1 + } +}; - // check for typical filename of a Sokoban level package file - if (strSuffixLower(basename, ".xsb") && - strchr(basename, '%') == NULL) - return LEVEL_FILE_TYPE_SB; +static struct LevelFileConfigInfo chunk_config_EMPX[] = +{ + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + }, - // ---------- try to determine file type from filesize ---------- + { + -1, -1, + -1, -1, + NULL, -1 + } +}; - checked_free(filename); - filename = getPath2(getCurrentLevelDir(), basename); +static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE) +{ + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), + &li.block_snap_field, TRUE + }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(13), + &li.continuous_snapping, TRUE + }, + { + EL_PLAYER_1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.initial_player_stepsize[0], STEPSIZE_NORMAL + }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(10), + &li.use_start_element[0], FALSE + }, + { + EL_PLAYER_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.start_element[0], EL_PLAYER_1 + }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(11), + &li.use_artwork_element[0], FALSE + }, + { + EL_PLAYER_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.artwork_element[0], EL_PLAYER_1 + }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(12), + &li.use_explosion_element[0], FALSE + }, + { + EL_PLAYER_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.explosion_element[0], EL_PLAYER_1 + }, - if (stat(filename, &file_status) == 0) { - // check for typical filesize of a Supaplex level package file - if (file_status.st_size == 170496) - return LEVEL_FILE_TYPE_SP; + -1, -1, + -1, -1, + NULL, -1 } +}; - return LEVEL_FILE_TYPE_UNKNOWN; +static struct +{ + int filetype; + char *id; } - -static int getFileTypeFromMagicBytes(char *filename, int type) +filetype_id_list[] = { - File *file; - - if ((file = openFile(filename, MODE_READ))) - { - char chunk_name[CHUNK_ID_LEN + 1]; - - getFileChunkBE(file, chunk_name, NULL); - - if (strEqual(chunk_name, "MMII") || - strEqual(chunk_name, "MIRR")) - type = LEVEL_FILE_TYPE_MM; + { LEVEL_FILE_TYPE_RND, "RND" }, + { LEVEL_FILE_TYPE_BD, "BD" }, + { LEVEL_FILE_TYPE_EM, "EM" }, + { LEVEL_FILE_TYPE_SP, "SP" }, + { LEVEL_FILE_TYPE_DX, "DX" }, + { LEVEL_FILE_TYPE_SB, "SB" }, + { LEVEL_FILE_TYPE_DC, "DC" }, + { LEVEL_FILE_TYPE_MM, "MM" }, + { LEVEL_FILE_TYPE_MM, "DF" }, + { -1, NULL }, +}; - closeFile(file); - } - return type; -} +// ============================================================================ +// level file functions +// ============================================================================ -static boolean checkForPackageFromBasename(char *basename) +static boolean check_special_flags(char *flag) { - // !!! WON'T WORK ANYMORE IF getFileTypeFromBasename() ALSO DETECTS !!! - // !!! SINGLE LEVELS (CURRENTLY ONLY DETECTS LEVEL PACKAGES !!! + if (strEqual(options.special_flags, flag) || + strEqual(leveldir_current->special_flags, flag)) + return TRUE; - return (getFileTypeFromBasename(basename) != LEVEL_FILE_TYPE_UNKNOWN); + return FALSE; } -static char *getSingleLevelBasenameExt(int nr, char *extension) +static struct DateInfo getCurrentDate(void) { - static char basename[MAX_FILENAME_LEN]; + time_t epoch_seconds = time(NULL); + struct tm *now = localtime(&epoch_seconds); + struct DateInfo date; - if (nr < 0) - sprintf(basename, "%s", LEVELTEMPLATE_FILENAME); - else - sprintf(basename, "%03d.%s", nr, extension); + date.year = now->tm_year + 1900; + date.month = now->tm_mon + 1; + date.day = now->tm_mday; - return basename; + date.src = DATE_SRC_CLOCK; + + return date; } -static char *getSingleLevelBasename(int nr) +static void resetEventFlags(struct ElementChangeInfo *change) { - return getSingleLevelBasenameExt(nr, LEVELFILE_EXTENSION); + int i; + + for (i = 0; i < NUM_CHANGE_EVENTS; i++) + change->has_event[i] = FALSE; } -static char *getPackedLevelBasename(int type) +static void resetEventBits(void) { - static char basename[MAX_FILENAME_LEN]; - char *directory = getCurrentLevelDir(); - Directory *dir; - DirectoryEntry *dir_entry; - - strcpy(basename, UNDEFINED_FILENAME); // default: undefined file + int i; - if ((dir = openDirectory(directory)) == NULL) - { - Warn("cannot read current level directory '%s'", directory); + for (i = 0; i < NUM_CE_BITFIELDS; i++) + xx_event_bits[i] = 0; +} - return basename; - } +static void setEventFlagsFromEventBits(struct ElementChangeInfo *change) +{ + int i; - while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries - { - char *entry_basename = dir_entry->basename; - int entry_type = getFileTypeFromBasename(entry_basename); + /* important: only change event flag if corresponding event bit is set + (this is because all xx_event_bits[] values are loaded separately, + and all xx_event_bits[] values are set back to zero before loading + another value xx_event_bits[x] (each value representing 32 flags)) */ - if (entry_type != LEVEL_FILE_TYPE_UNKNOWN) // found valid level package - { - if (type == LEVEL_FILE_TYPE_UNKNOWN || - type == entry_type) - { - strcpy(basename, entry_basename); + for (i = 0; i < NUM_CHANGE_EVENTS; i++) + if (xx_event_bits[CH_EVENT_BITFIELD_NR(i)] & CH_EVENT_BIT(i)) + change->has_event[i] = TRUE; +} - break; - } - } - } +static void setEventBitsFromEventFlags(struct ElementChangeInfo *change) +{ + int i; - closeDirectory(dir); + /* in contrast to the above function setEventFlagsFromEventBits(), it + would also be possible to set all bits in xx_event_bits[] to 0 or 1 + depending on the corresponding change->has_event[i] values here, as + all xx_event_bits[] values are reset in resetEventBits() before */ - return basename; + for (i = 0; i < NUM_CHANGE_EVENTS; i++) + if (change->has_event[i]) + xx_event_bits[CH_EVENT_BITFIELD_NR(i)] |= CH_EVENT_BIT(i); } -static char *getSingleLevelFilename(int nr) +static char *getDefaultElementDescription(struct ElementInfo *ei) { - return getLevelFilenameFromBasename(getSingleLevelBasename(nr)); -} + static char description[MAX_ELEMENT_NAME_LEN + 1]; + char *default_description = (ei->custom_description != NULL ? + ei->custom_description : + ei->editor_description); + int i; -#if ENABLE_UNUSED_CODE -static char *getPackedLevelFilename(int type) -{ - return getLevelFilenameFromBasename(getPackedLevelBasename(type)); -} -#endif + // always start with reliable default values + for (i = 0; i < MAX_ELEMENT_NAME_LEN + 1; i++) + description[i] = '\0'; -char *getDefaultLevelFilename(int nr) -{ - return getSingleLevelFilename(nr); + // truncate element description to MAX_ELEMENT_NAME_LEN bytes + strncpy(description, default_description, MAX_ELEMENT_NAME_LEN); + + return &description[0]; } -#if ENABLE_UNUSED_CODE -static void setLevelFileInfo_SingleLevelFilename(struct LevelFileInfo *lfi, - int type) +static void setElementDescriptionToDefault(struct ElementInfo *ei) { - lfi->type = type; - lfi->packed = FALSE; + char *default_description = getDefaultElementDescription(ei); + int i; - setString(&lfi->basename, getSingleLevelBasename(lfi->nr, lfi->type)); - setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); + for (i = 0; i < MAX_ELEMENT_NAME_LEN + 1; i++) + ei->description[i] = default_description[i]; } -#endif -static void setLevelFileInfo_FormatLevelFilename(struct LevelFileInfo *lfi, - int type, char *format, ...) +static void setConfigToDefaultsFromConfigList(struct LevelFileConfigInfo *conf) { - static char basename[MAX_FILENAME_LEN]; - va_list ap; + int i; - va_start(ap, format); - vsprintf(basename, format, ap); - va_end(ap); + for (i = 0; conf[i].data_type != -1; i++) + { + int default_value = conf[i].default_value; + int data_type = conf[i].data_type; + int conf_type = conf[i].conf_type; + int byte_mask = conf_type & CONF_MASK_BYTES; - lfi->type = type; - lfi->packed = FALSE; + if (byte_mask == CONF_MASK_MULTI_BYTES) + { + int default_num_entities = conf[i].default_num_entities; + int max_num_entities = conf[i].max_num_entities; - setString(&lfi->basename, basename); - setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); -} + *(int *)(conf[i].num_entities) = default_num_entities; -static void setLevelFileInfo_PackedLevelFilename(struct LevelFileInfo *lfi, - int type) -{ - lfi->type = type; - lfi->packed = TRUE; + if (data_type == TYPE_STRING) + { + char *default_string = conf[i].default_string; + char *string = (char *)(conf[i].value); - setString(&lfi->basename, getPackedLevelBasename(lfi->type)); - setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); -} + strncpy(string, default_string, max_num_entities); + } + else if (data_type == TYPE_ELEMENT_LIST) + { + int *element_array = (int *)(conf[i].value); + int j; -static int getFiletypeFromID(char *filetype_id) -{ - char *filetype_id_lower; - int filetype = LEVEL_FILE_TYPE_UNKNOWN; - int i; + for (j = 0; j < max_num_entities; j++) + element_array[j] = default_value; + } + else if (data_type == TYPE_CONTENT_LIST) + { + struct Content *content = (struct Content *)(conf[i].value); + int c, x, y; - if (filetype_id == NULL) - return LEVEL_FILE_TYPE_UNKNOWN; + for (c = 0; c < max_num_entities; c++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content[c].e[x][y] = default_value; + } + } + else // constant size configuration data (1, 2 or 4 bytes) + { + if (data_type == TYPE_BOOLEAN) + *(boolean *)(conf[i].value) = default_value; + else + *(int *) (conf[i].value) = default_value; + } + } +} - filetype_id_lower = getStringToLower(filetype_id); +static void copyConfigFromConfigList(struct LevelFileConfigInfo *conf) +{ + int i; - for (i = 0; filetype_id_list[i].id != NULL; i++) + for (i = 0; conf[i].data_type != -1; i++) { - char *id_lower = getStringToLower(filetype_id_list[i].id); - - if (strEqual(filetype_id_lower, id_lower)) - filetype = filetype_id_list[i].filetype; + int data_type = conf[i].data_type; + int conf_type = conf[i].conf_type; + int byte_mask = conf_type & CONF_MASK_BYTES; - free(id_lower); + if (byte_mask == CONF_MASK_MULTI_BYTES) + { + int max_num_entities = conf[i].max_num_entities; - if (filetype != LEVEL_FILE_TYPE_UNKNOWN) - break; - } + if (data_type == TYPE_STRING) + { + char *string = (char *)(conf[i].value); + char *string_copy = (char *)(conf[i].value_copy); - free(filetype_id_lower); + strncpy(string_copy, string, max_num_entities); + } + else if (data_type == TYPE_ELEMENT_LIST) + { + int *element_array = (int *)(conf[i].value); + int *element_array_copy = (int *)(conf[i].value_copy); + int j; - return filetype; -} + for (j = 0; j < max_num_entities; j++) + element_array_copy[j] = element_array[j]; + } + else if (data_type == TYPE_CONTENT_LIST) + { + struct Content *content = (struct Content *)(conf[i].value); + struct Content *content_copy = (struct Content *)(conf[i].value_copy); + int c, x, y; -char *getLocalLevelTemplateFilename(void) -{ - return getDefaultLevelFilename(-1); + for (c = 0; c < max_num_entities; c++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content_copy[c].e[x][y] = content[c].e[x][y]; + } + } + else // constant size configuration data (1, 2 or 4 bytes) + { + if (data_type == TYPE_BOOLEAN) + *(boolean *)(conf[i].value_copy) = *(boolean *)(conf[i].value); + else + *(int *) (conf[i].value_copy) = *(int *) (conf[i].value); + } + } } -char *getGlobalLevelTemplateFilename(void) +void copyElementInfo(struct ElementInfo *ei_from, struct ElementInfo *ei_to) { - // global variable "leveldir_current" must be modified in the loop below - LevelDirTree *leveldir_current_last = leveldir_current; - char *filename = NULL; - - // check for template level in path from current to topmost tree node - - while (leveldir_current != NULL) - { - filename = getDefaultLevelFilename(-1); + int i; - if (fileExists(filename)) - break; + xx_ei = *ei_from; // copy element data into temporary buffer + yy_ei = *ei_to; // copy element data into temporary buffer - leveldir_current = leveldir_current->node_parent; - } + copyConfigFromConfigList(chunk_config_CUSX_base); - // restore global variable "leveldir_current" modified in above loop - leveldir_current = leveldir_current_last; + *ei_from = xx_ei; + *ei_to = yy_ei; - return filename; -} + // ---------- reinitialize and copy change pages ---------- -static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi) -{ - int nr = lfi->nr; + ei_to->num_change_pages = ei_from->num_change_pages; + ei_to->current_change_page = ei_from->current_change_page; - // special case: level number is negative => check for level template file - if (nr < 0) - { - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, - getSingleLevelBasename(-1)); + setElementChangePages(ei_to, ei_to->num_change_pages); - // replace local level template filename with global template filename - setString(&lfi->filename, getGlobalLevelTemplateFilename()); + for (i = 0; i < ei_to->num_change_pages; i++) + ei_to->change_page[i] = ei_from->change_page[i]; - // no fallback if template file not existing - return; - } + // ---------- copy group element info ---------- + if (ei_from->group != NULL && ei_to->group != NULL) // group or internal + *ei_to->group = *ei_from->group; - // special case: check for file name/pattern specified in "levelinfo.conf" - if (leveldir_current->level_filename != NULL) - { - int filetype = getFiletypeFromID(leveldir_current->level_filetype); + // mark this custom element as modified + ei_to->modified_settings = TRUE; +} - setLevelFileInfo_FormatLevelFilename(lfi, filetype, - leveldir_current->level_filename, nr); +void setElementChangePages(struct ElementInfo *ei, int change_pages) +{ + int change_page_size = sizeof(struct ElementChangeInfo); - lfi->packed = checkForPackageFromBasename(leveldir_current->level_filename); + ei->num_change_pages = MAX(1, change_pages); - if (fileExists(lfi->filename)) - return; - } - else if (leveldir_current->level_filetype != NULL) - { - int filetype = getFiletypeFromID(leveldir_current->level_filetype); + ei->change_page = + checked_realloc(ei->change_page, ei->num_change_pages * change_page_size); - // check for specified native level file with standard file name - setLevelFileInfo_FormatLevelFilename(lfi, filetype, - "%03d.%s", nr, LEVELFILE_EXTENSION); - if (fileExists(lfi->filename)) - return; - } + if (ei->current_change_page >= ei->num_change_pages) + ei->current_change_page = ei->num_change_pages - 1; - // check for native Rocks'n'Diamonds level file - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, - "%03d.%s", nr, LEVELFILE_EXTENSION); - if (fileExists(lfi->filename)) - return; + ei->change = &ei->change_page[ei->current_change_page]; +} - // check for Emerald Mine level file (V1) - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "a%c%c", - 'a' + (nr / 10) % 26, '0' + nr % 10); - if (fileExists(lfi->filename)) - return; - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "A%c%c", - 'A' + (nr / 10) % 26, '0' + nr % 10); - if (fileExists(lfi->filename)) - return; +void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) +{ + xx_change = *change; // copy change data into temporary buffer - // check for Emerald Mine level file (V2 to V5) - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%d", nr); - if (fileExists(lfi->filename)) - return; + setConfigToDefaultsFromConfigList(chunk_config_CUSX_change); - // check for Emerald Mine level file (V6 / single mode) - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02ds", nr); - if (fileExists(lfi->filename)) - return; - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dS", nr); - if (fileExists(lfi->filename)) - return; + *change = xx_change; - // check for Emerald Mine level file (V6 / teamwork mode) - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dt", nr); - if (fileExists(lfi->filename)) - return; - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dT", nr); - if (fileExists(lfi->filename)) - return; + resetEventFlags(change); - // check for various packed level file formats - setLevelFileInfo_PackedLevelFilename(lfi, LEVEL_FILE_TYPE_UNKNOWN); - if (fileExists(lfi->filename)) - return; + change->direct_action = 0; + change->other_action = 0; - // no known level file found -- use default values (and fail later) - setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, - "%03d.%s", nr, LEVELFILE_EXTENSION); + change->pre_change_function = NULL; + change->change_function = NULL; + change->post_change_function = NULL; } -static void determineLevelFileInfo_Filetype(struct LevelFileInfo *lfi) +static void setLevelInfoToDefaults_Level(struct LevelInfo *level) { - if (lfi->type == LEVEL_FILE_TYPE_UNKNOWN) - lfi->type = getFileTypeFromBasename(lfi->basename); + boolean add_border = FALSE; + int x1 = 0; + int y1 = 0; + int x2 = STD_LEV_FIELDX - 1; + int y2 = STD_LEV_FIELDY - 1; + int i, x, y; - if (lfi->type == LEVEL_FILE_TYPE_RND) - lfi->type = getFileTypeFromMagicBytes(lfi->filename, lfi->type); -} + li = *level; // copy level data into temporary buffer + setConfigToDefaultsFromConfigList(chunk_config_INFO); + *level = li; // copy temporary buffer back to level data -static void setLevelFileInfo(struct LevelFileInfo *level_file_info, int nr) -{ - // always start with reliable default values - setFileInfoToDefaults(level_file_info); + setLevelInfoToDefaults_BD(); + setLevelInfoToDefaults_EM(); + setLevelInfoToDefaults_SP(); + setLevelInfoToDefaults_MM(); - level_file_info->nr = nr; // set requested level number + level->native_bd_level = &native_bd_level; + level->native_em_level = &native_em_level; + level->native_sp_level = &native_sp_level; + level->native_mm_level = &native_mm_level; - determineLevelFileInfo_Filename(level_file_info); - determineLevelFileInfo_Filetype(level_file_info); -} + level->file_version = FILE_VERSION_ACTUAL; + level->game_version = GAME_VERSION_ACTUAL; -static void copyLevelFileInfo(struct LevelFileInfo *lfi_from, - struct LevelFileInfo *lfi_to) -{ - lfi_to->nr = lfi_from->nr; - lfi_to->type = lfi_from->type; - lfi_to->packed = lfi_from->packed; + level->creation_date = getCurrentDate(); - setString(&lfi_to->basename, lfi_from->basename); - setString(&lfi_to->filename, lfi_from->filename); -} + level->encoding_16bit_field = TRUE; + level->encoding_16bit_yamyam = TRUE; + level->encoding_16bit_amoeba = TRUE; -// ---------------------------------------------------------------------------- -// functions for loading R'n'D level -// ---------------------------------------------------------------------------- + // clear level name and level author string buffers + for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) + level->name[i] = '\0'; + for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) + level->author[i] = '\0'; -int getMappedElement(int element) -{ - // remap some (historic, now obsolete) elements + // set level name and level author to default values + strcpy(level->name, NAMELESS_LEVEL_NAME); + strcpy(level->author, ANONYMOUS_NAME); - switch (element) + // set default game engine type + level->game_engine_type = setup.default_game_engine_type; + + // some game engines should have a default playfield with border elements + if (level->game_engine_type == GAME_ENGINE_TYPE_BD || + level->game_engine_type == GAME_ENGINE_TYPE_EM || + level->game_engine_type == GAME_ENGINE_TYPE_SP) { - case EL_PLAYER_OBSOLETE: - element = EL_PLAYER_1; - break; + add_border = TRUE; + x1++; + y1++; + x2--; + y2--; + } - case EL_KEY_OBSOLETE: - element = EL_KEY_1; - break; + // set level playfield to playable default level with player and exit + for (x = 0; x < MAX_LEV_FIELDX; x++) + { + for (y = 0; y < MAX_LEV_FIELDY; y++) + { + if (add_border && (x == 0 || x == STD_LEV_FIELDX - 1 || + y == 0 || y == STD_LEV_FIELDY - 1)) + level->field[x][y] = getEngineElement(EL_STEELWALL); + else + level->field[x][y] = getEngineElement(EL_SAND); + } + } - case EL_EM_KEY_1_FILE_OBSOLETE: - element = EL_EM_KEY_1; - break; + level->field[x1][y1] = getEngineElement(EL_PLAYER_1); + level->field[x2][y2] = getEngineElement(EL_EXIT_CLOSED); - case EL_EM_KEY_2_FILE_OBSOLETE: - element = EL_EM_KEY_2; - break; + BorderElement = getEngineElement(EL_STEELWALL); - case EL_EM_KEY_3_FILE_OBSOLETE: - element = EL_EM_KEY_3; - break; + // detect custom elements when loading them + level->file_has_custom_elements = FALSE; - case EL_EM_KEY_4_FILE_OBSOLETE: - element = EL_EM_KEY_4; - break; + // set random colors for BD style levels according to preferred color type + SetRandomLevelColors_BD(setup.bd_default_color_type); - case EL_ENVELOPE_OBSOLETE: - element = EL_ENVELOPE_1; - break; + // set default color type and colors for BD style level colors + SetDefaultLevelColorType_BD(); + SetDefaultLevelColors_BD(); - case EL_SP_EMPTY: - element = EL_EMPTY; - break; + // set all bug compatibility flags to "false" => do not emulate this bug + level->use_action_after_change_bug = FALSE; - default: - if (element >= NUM_FILE_ELEMENTS) + if (leveldir_current) + { + // try to determine better author name than 'anonymous' + if (!strEqual(leveldir_current->author, ANONYMOUS_NAME)) + { + strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + } + else + { + switch (LEVELCLASS(leveldir_current)) { - Warn("invalid level element %d", element); + case LEVELCLASS_TUTORIAL: + strcpy(level->author, PROGRAM_AUTHOR_STRING); + break; - element = EL_UNKNOWN; + case LEVELCLASS_CONTRIB: + strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + break; + + case LEVELCLASS_PRIVATE: + strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + break; + + default: + // keep default value + break; } - break; + } } - - return element; } -static int getMappedElementByVersion(int element, int game_version) +static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) { - // remap some elements due to certain game version + static boolean clipboard_elements_initialized = FALSE; + int i; - 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); - } + InitElementPropertiesStatic(); - if (game_version < VERSION_IDENT(3,0,0,0)) + li = *level; // copy level data into temporary buffer + setConfigToDefaultsFromConfigList(chunk_config_ELEM); + *level = li; // copy temporary buffer back to level data + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) { - // 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); - } + int element = i; + struct ElementInfo *ei = &element_info[element]; - return element; -} + if (element == EL_MM_GRAY_BALL) + { + struct LevelInfo_MM *level_mm = level->native_mm_level; + int j; -static int LoadLevel_VERS(File *file, int chunk_size, struct LevelInfo *level) -{ - level->file_version = getFileVersion(file); - level->game_version = getFileVersion(file); + for (j = 0; j < level->num_mm_ball_contents; j++) + level->mm_ball_content[j] = + map_element_MM_to_RND(level_mm->ball_content[j]); + } - return chunk_size; -} + // never initialize clipboard elements after the very first time + // (to be able to use clipboard elements between several levels) + if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized) + continue; -static int LoadLevel_DATE(File *file, int chunk_size, struct LevelInfo *level) -{ - level->creation_date.year = getFile16BitBE(file); - level->creation_date.month = getFile8Bit(file); - level->creation_date.day = getFile8Bit(file); + if (IS_ENVELOPE(element)) + { + int envelope_nr = element - EL_ENVELOPE_1; - level->creation_date.src = DATE_SRC_LEVELFILE; + setConfigToDefaultsFromConfigList(chunk_config_NOTE); - return chunk_size; -} + level->envelope[envelope_nr] = xx_envelope; + } -static int LoadLevel_HEAD(File *file, int chunk_size, struct LevelInfo *level) -{ - int initial_player_stepsize; - int initial_player_gravity; - int i, x, y; + if (IS_CUSTOM_ELEMENT(element) || + IS_GROUP_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + xx_ei = *ei; // copy element data into temporary buffer - level->fieldx = getFile8Bit(file); - level->fieldy = getFile8Bit(file); + setConfigToDefaultsFromConfigList(chunk_config_CUSX_base); - level->time = getFile16BitBE(file); - level->gems_needed = getFile16BitBE(file); + *ei = xx_ei; + } - for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) - level->name[i] = getFile8Bit(file); - level->name[MAX_LEVEL_NAME_LEN] = 0; + setElementChangePages(ei, 1); + setElementChangeInfoToDefaults(ei->change); - for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) - level->score[i] = getFile8Bit(file); + if (IS_CUSTOM_ELEMENT(element) || + IS_GROUP_ELEMENT(element)) + { + setElementDescriptionToDefault(ei); - level->num_yamyam_contents = STD_ELEMENT_CONTENTS; - for (i = 0; i < STD_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - level->yamyam_content[i].e[x][y] = getMappedElement(getFile8Bit(file)); + ei->modified_settings = FALSE; + } - level->amoeba_speed = getFile8Bit(file); - level->time_magic_wall = getFile8Bit(file); - level->time_wheel = getFile8Bit(file); - level->amoeba_content = getMappedElement(getFile8Bit(file)); + if (IS_CUSTOM_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + // internal values used in level editor - initial_player_stepsize = (getFile8Bit(file) == 1 ? STEPSIZE_FAST : - STEPSIZE_NORMAL); + ei->access_type = 0; + ei->access_layer = 0; + ei->access_protected = 0; + ei->walk_to_action = 0; + ei->smash_targets = 0; + ei->deadliness = 0; - for (i = 0; i < MAX_PLAYERS; i++) - level->initial_player_stepsize[i] = initial_player_stepsize; + ei->can_explode_by_fire = FALSE; + ei->can_explode_smashed = FALSE; + ei->can_explode_impact = FALSE; - initial_player_gravity = (getFile8Bit(file) == 1 ? TRUE : FALSE); + ei->current_change_page = 0; + } - for (i = 0; i < MAX_PLAYERS; i++) - level->initial_player_gravity[i] = initial_player_gravity; + if (IS_GROUP_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + struct ElementGroupInfo *group; - level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); - level->em_slippery_gems = (getFile8Bit(file) == 1 ? TRUE : FALSE); + // initialize memory for list of elements in group + if (ei->group == NULL) + ei->group = checked_malloc(sizeof(struct ElementGroupInfo)); - level->use_custom_template = (getFile8Bit(file) == 1 ? TRUE : FALSE); + group = ei->group; - 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); + xx_group = *group; // copy group data into temporary buffer - level->use_spring_bug = (getFile8Bit(file) == 1 ? TRUE : FALSE); - level->use_step_counter = (getFile8Bit(file) == 1 ? TRUE : FALSE); + setConfigToDefaultsFromConfigList(chunk_config_GRPX); - 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); + *group = xx_group; + } - level->game_engine_type = getFile8Bit(file); + if (IS_EMPTY_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + xx_ei = *ei; // copy element data into temporary buffer - ReadUnusedBytesFromFile(file, LEVEL_CHUNK_HEAD_UNUSED); + setConfigToDefaultsFromConfigList(chunk_config_EMPX); - return chunk_size; + *ei = xx_ei; + } + } + + clipboard_elements_initialized = TRUE; } -static int LoadLevel_NAME(File *file, int chunk_size, struct LevelInfo *level) +static void setLevelInfoToDefaults(struct LevelInfo *level, + boolean level_info_only, + boolean reset_file_status) { - int i; + setLevelInfoToDefaults_Level(level); - for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) - level->name[i] = getFile8Bit(file); - level->name[MAX_LEVEL_NAME_LEN] = 0; + if (!level_info_only) + setLevelInfoToDefaults_Elements(level); - return chunk_size; + if (reset_file_status) + { + level->no_valid_file = FALSE; + level->no_level_file = FALSE; + } + + level->changed = FALSE; +} + +static void setFileInfoToDefaults(struct LevelFileInfo *level_file_info) +{ + level_file_info->nr = 0; + level_file_info->type = LEVEL_FILE_TYPE_UNKNOWN; + level_file_info->packed = FALSE; + + setString(&level_file_info->basename, NULL); + setString(&level_file_info->filename, NULL); } -static int LoadLevel_AUTH(File *file, int chunk_size, struct LevelInfo *level) -{ - int i; +int getMappedElement_SB(int, boolean); + +static void ActivateLevelTemplate(void) +{ + int x, y; + + if (check_special_flags("load_xsb_to_ces")) + { + // fill smaller playfields with padding "beyond border wall" elements + if (level.fieldx < level_template.fieldx || + level.fieldy < level_template.fieldy) + { + short field[level.fieldx][level.fieldy]; + int new_fieldx = MAX(level.fieldx, level_template.fieldx); + int new_fieldy = MAX(level.fieldy, level_template.fieldy); + int pos_fieldx = (new_fieldx - level.fieldx) / 2; + int pos_fieldy = (new_fieldy - level.fieldy) / 2; + + // copy old playfield (which is smaller than the visible area) + for (y = 0; y < level.fieldy; y++) for (x = 0; x < level.fieldx; x++) + field[x][y] = level.field[x][y]; - for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) - level->author[i] = getFile8Bit(file); - level->author[MAX_LEVEL_AUTHOR_LEN] = 0; + // fill new, larger playfield with "beyond border wall" elements + for (y = 0; y < new_fieldy; y++) for (x = 0; x < new_fieldx; x++) + level.field[x][y] = getMappedElement_SB('_', TRUE); - return chunk_size; -} + // copy the old playfield to the middle of the new playfield + for (y = 0; y < level.fieldy; y++) for (x = 0; x < level.fieldx; x++) + level.field[pos_fieldx + x][pos_fieldy + y] = field[x][y]; -static int LoadLevel_BODY(File *file, int chunk_size, struct LevelInfo *level) -{ - int x, y; - int chunk_size_expected = level->fieldx * level->fieldy; + level.fieldx = new_fieldx; + level.fieldy = new_fieldy; + } + } - /* Note: "chunk_size" was wrong before version 2.0 when elements are - stored with 16-bit encoding (and should be twice as big then). - Even worse, playfield data was stored 16-bit when only yamyam content - contained 16-bit elements and vice versa. */ + // Currently there is no special action needed to activate the template + // data, because 'element_info' property settings overwrite the original + // level data, while all other variables do not change. - if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) - chunk_size_expected *= 2; + // Exception: 'from_level_template' elements in the original level playfield + // are overwritten with the corresponding elements at the same position in + // playfield from the level template. - if (chunk_size_expected != chunk_size) + for (x = 0; x < level.fieldx; x++) + for (y = 0; y < level.fieldy; y++) + if (level.field[x][y] == EL_FROM_LEVEL_TEMPLATE) + level.field[x][y] = level_template.field[x][y]; + + if (check_special_flags("load_xsb_to_ces")) { - ReadUnusedBytesFromFile(file, chunk_size); - return chunk_size_expected; - } + struct LevelInfo level_backup = level; - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) - level->field[x][y] = - getMappedElement(level->encoding_16bit_field ? getFile16BitBE(file) : - getFile8Bit(file)); - return chunk_size; -} + // overwrite all individual level settings from template level settings + level = level_template; -static int LoadLevel_CONT(File *file, int chunk_size, struct LevelInfo *level) -{ - int i, x, y; - int header_size = 4; - int content_size = MAX_ELEMENT_CONTENTS * 3 * 3; - int chunk_size_expected = header_size + content_size; + // restore level file info + level.file_info = level_backup.file_info; - /* Note: "chunk_size" was wrong before version 2.0 when elements are - stored with 16-bit encoding (and should be twice as big then). - Even worse, playfield data was stored 16-bit when only yamyam content - contained 16-bit elements and vice versa. */ + // restore playfield size + level.fieldx = level_backup.fieldx; + level.fieldy = level_backup.fieldy; - if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) - chunk_size_expected += content_size; + // restore playfield content + for (x = 0; x < level.fieldx; x++) + for (y = 0; y < level.fieldy; y++) + level.field[x][y] = level_backup.field[x][y]; - if (chunk_size_expected != chunk_size) - { - ReadUnusedBytesFromFile(file, chunk_size); - return chunk_size_expected; + // restore name and author from individual level + strcpy(level.name, level_backup.name); + strcpy(level.author, level_backup.author); + + // restore flag "use_custom_template" + level.use_custom_template = level_backup.use_custom_template; } +} - getFile8Bit(file); - level->num_yamyam_contents = getFile8Bit(file); - getFile8Bit(file); - getFile8Bit(file); +static boolean checkForPackageFromBasename_BD(char *basename) +{ + // check for native BD level file extensions + if (!strSuffixLower(basename, ".bd") && + !strSuffixLower(basename, ".bdr") && + !strSuffixLower(basename, ".brc") && + !strSuffixLower(basename, ".gds")) + return FALSE; - // correct invalid number of content fields -- should never happen - if (level->num_yamyam_contents < 1 || - level->num_yamyam_contents > MAX_ELEMENT_CONTENTS) - level->num_yamyam_contents = STD_ELEMENT_CONTENTS; + // check for standard single-level BD files (like "001.bd") + if (strSuffixLower(basename, ".bd") && + strlen(basename) == 6 && + basename[0] >= '0' && basename[0] <= '9' && + basename[1] >= '0' && basename[1] <= '9' && + basename[2] >= '0' && basename[2] <= '9') + return FALSE; - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - level->yamyam_content[i].e[x][y] = - getMappedElement(level->encoding_16bit_field ? - getFile16BitBE(file) : getFile8Bit(file)); - return chunk_size; + // this is a level package in native BD file format + return TRUE; } -static int LoadLevel_CNT2(File *file, int chunk_size, struct LevelInfo *level) +static char *getLevelFilenameFromBasename(char *basename) { - int i, x, y; - int element; - int num_contents; - int content_array[MAX_ELEMENT_CONTENTS][3][3]; - - element = getMappedElement(getFile16BitBE(file)); - num_contents = getFile8Bit(file); - - getFile8Bit(file); // content x size (unused) - getFile8Bit(file); // content y size (unused) + static char *filename = NULL; - ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED); + checked_free(filename); - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content_array[i][x][y] = getMappedElement(getFile16BitBE(file)); + filename = getPath2(getCurrentLevelDir(), basename); - // correct invalid number of content fields -- should never happen - if (num_contents < 1 || num_contents > MAX_ELEMENT_CONTENTS) - num_contents = STD_ELEMENT_CONTENTS; + return filename; +} - if (element == EL_YAMYAM) - { - level->num_yamyam_contents = num_contents; +static int getFileTypeFromBasename(char *basename) +{ + // !!! ALSO SEE COMMENT IN checkForPackageFromBasename() !!! - for (i = 0; i < num_contents; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - level->yamyam_content[i].e[x][y] = content_array[i][x][y]; - } - else if (element == EL_BD_AMOEBA) - { - level->amoeba_content = content_array[0][0][0]; - } - else - { - Warn("cannot load content for element '%d'", element); - } + static char *filename = NULL; + struct stat file_status; - return chunk_size; -} + // ---------- try to determine file type from filename ---------- -static int LoadLevel_CNT3(File *file, int chunk_size, struct LevelInfo *level) -{ - int i; - int element; - int envelope_nr; - int envelope_len; - int chunk_size_expected; + // check for typical filename of a Supaplex level package file + if (strlen(basename) == 10 && strPrefixLower(basename, "levels.d")) + return LEVEL_FILE_TYPE_SP; - element = getMappedElement(getFile16BitBE(file)); - if (!IS_ENVELOPE(element)) - element = EL_ENVELOPE_1; + // check for typical filename of a Diamond Caves II level package file + if (strSuffixLower(basename, ".dc") || + strSuffixLower(basename, ".dc2")) + return LEVEL_FILE_TYPE_DC; - envelope_nr = element - EL_ENVELOPE_1; + // check for typical filename of a Sokoban level package file + if (strSuffixLower(basename, ".xsb") && + strchr(basename, '%') == NULL) + return LEVEL_FILE_TYPE_SB; - envelope_len = getFile16BitBE(file); + // check for typical filename of a Boulder Dash (GDash) level package file + if (checkForPackageFromBasename_BD(basename)) + return LEVEL_FILE_TYPE_BD; - level->envelope[envelope_nr].xsize = getFile8Bit(file); - level->envelope[envelope_nr].ysize = getFile8Bit(file); + // ---------- try to determine file type from filesize ---------- - ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT3_UNUSED); + checked_free(filename); + filename = getPath2(getCurrentLevelDir(), basename); - chunk_size_expected = LEVEL_CHUNK_CNT3_SIZE(envelope_len); - if (chunk_size_expected != chunk_size) + if (stat(filename, &file_status) == 0) { - ReadUnusedBytesFromFile(file, chunk_size - LEVEL_CHUNK_CNT3_HEADER); - return chunk_size_expected; + // check for typical filesize of a Supaplex level package file + if (file_status.st_size == 170496) + return LEVEL_FILE_TYPE_SP; } - for (i = 0; i < envelope_len; i++) - level->envelope[envelope_nr].text[i] = getFile8Bit(file); - - return chunk_size; + return LEVEL_FILE_TYPE_UNKNOWN; } -static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level) +static int getFileTypeFromMagicBytes(char *filename, int type) { - int num_changed_custom_elements = getFile16BitBE(file); - int chunk_size_expected = 2 + num_changed_custom_elements * 6; - int i; + File *file; - if (chunk_size_expected != chunk_size) + if ((file = openFile(filename, MODE_READ))) { - ReadUnusedBytesFromFile(file, chunk_size - 2); - return chunk_size_expected; - } + char chunk_name[CHUNK_ID_LEN + 1]; - for (i = 0; i < num_changed_custom_elements; i++) - { - int element = getMappedElement(getFile16BitBE(file)); - int properties = getFile32BitBE(file); + getFileChunkBE(file, chunk_name, NULL); - if (IS_CUSTOM_ELEMENT(element)) - element_info[element].properties[EP_BITFIELD_BASE_NR] = properties; - else - Warn("invalid custom element number %d", element); + if (strEqual(chunk_name, "MMII") || + strEqual(chunk_name, "MIRR")) + type = LEVEL_FILE_TYPE_MM; - // older game versions that wrote level files with CUS1 chunks used - // different default push delay values (not yet stored in level file) - element_info[element].push_delay_fixed = 2; - element_info[element].push_delay_random = 8; + closeFile(file); } - level->file_has_custom_elements = TRUE; - - return chunk_size; + return type; } -static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level) +static boolean checkForPackageFromBasename(char *basename) { - int num_changed_custom_elements = getFile16BitBE(file); - int chunk_size_expected = 2 + num_changed_custom_elements * 4; - int i; - - if (chunk_size_expected != chunk_size) - { - ReadUnusedBytesFromFile(file, chunk_size - 2); - return chunk_size_expected; - } + // !!! WON'T WORK ANYMORE IF getFileTypeFromBasename() ALSO DETECTS !!! + // !!! SINGLE LEVELS (CURRENTLY ONLY DETECTS LEVEL PACKAGES !!! - for (i = 0; i < num_changed_custom_elements; i++) - { - int element = getMappedElement(getFile16BitBE(file)); - int custom_target_element = getMappedElement(getFile16BitBE(file)); + return (getFileTypeFromBasename(basename) != LEVEL_FILE_TYPE_UNKNOWN); +} - if (IS_CUSTOM_ELEMENT(element)) - element_info[element].change->target_element = custom_target_element; - else - Warn("invalid custom element number %d", element); - } +static char *getSingleLevelBasenameExt(int nr, char *extension) +{ + static char basename[MAX_FILENAME_LEN]; - level->file_has_custom_elements = TRUE; + if (nr < 0) + sprintf(basename, "%s", LEVELTEMPLATE_FILENAME); + else + sprintf(basename, "%03d.%s", nr, extension); - return chunk_size; + return basename; } -static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) +static char *getSingleLevelBasename(int nr) { - int num_changed_custom_elements = getFile16BitBE(file); - int chunk_size_expected = LEVEL_CHUNK_CUS3_SIZE(num_changed_custom_elements); - int i, j, x, y; + return getSingleLevelBasenameExt(nr, LEVELFILE_EXTENSION); +} - if (chunk_size_expected != chunk_size) +static char *getPackedLevelBasename(int type) +{ + static char basename[MAX_FILENAME_LEN]; + char *directory = getCurrentLevelDir(); + Directory *dir; + DirectoryEntry *dir_entry; + + strcpy(basename, UNDEFINED_FILENAME); // default: undefined file + + if ((dir = openDirectory(directory)) == NULL) { - ReadUnusedBytesFromFile(file, chunk_size - 2); - return chunk_size_expected; + Warn("cannot read current level directory '%s'", directory); + + return basename; } - for (i = 0; i < num_changed_custom_elements; i++) + while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries { - int element = getMappedElement(getFile16BitBE(file)); - struct ElementInfo *ei = &element_info[element]; - unsigned int event_bits; + char *entry_basename = dir_entry->basename; + int entry_type = getFileTypeFromBasename(entry_basename); - if (!IS_CUSTOM_ELEMENT(element)) + if (entry_type != LEVEL_FILE_TYPE_UNKNOWN) // found valid level package { - Warn("invalid custom element number %d", element); + if (type == LEVEL_FILE_TYPE_UNKNOWN || + type == entry_type) + { + strcpy(basename, entry_basename); - element = EL_INTERNAL_DUMMY; + break; + } } + } - for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++) - ei->description[j] = getFile8Bit(file); - ei->description[MAX_ELEMENT_NAME_LEN] = 0; + closeDirectory(dir); - ei->properties[EP_BITFIELD_BASE_NR] = getFile32BitBE(file); + return basename; +} - // some free bytes for future properties and padding - ReadUnusedBytesFromFile(file, 7); +static char *getSingleLevelFilename(int nr) +{ + return getLevelFilenameFromBasename(getSingleLevelBasename(nr)); +} - ei->use_gfx_element = getFile8Bit(file); - ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); +#if ENABLE_UNUSED_CODE +static char *getPackedLevelFilename(int type) +{ + return getLevelFilenameFromBasename(getPackedLevelBasename(type)); +} +#endif - ei->collect_score_initial = getFile8Bit(file); - ei->collect_count_initial = getFile8Bit(file); +char *getDefaultLevelFilename(int nr) +{ + return getSingleLevelFilename(nr); +} - ei->push_delay_fixed = getFile16BitBE(file); - ei->push_delay_random = getFile16BitBE(file); - ei->move_delay_fixed = getFile16BitBE(file); - ei->move_delay_random = getFile16BitBE(file); +#if ENABLE_UNUSED_CODE +static void setLevelFileInfo_SingleLevelFilename(struct LevelFileInfo *lfi, + int type) +{ + lfi->type = type; + lfi->packed = FALSE; - ei->move_pattern = getFile16BitBE(file); - ei->move_direction_initial = getFile8Bit(file); - ei->move_stepsize = getFile8Bit(file); + setString(&lfi->basename, getSingleLevelBasename(lfi->nr, lfi->type)); + setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); +} +#endif - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); +static void setLevelFileInfo_FormatLevelFilename(struct LevelFileInfo *lfi, + int type, char *format, ...) +{ + static char basename[MAX_FILENAME_LEN]; + va_list ap; - // bits 0 - 31 of "has_event[]" - event_bits = getFile32BitBE(file); - for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) - if (event_bits & (1 << j)) - ei->change->has_event[j] = TRUE; + va_start(ap, format); + vsprintf(basename, format, ap); + va_end(ap); - ei->change->target_element = getMappedElement(getFile16BitBE(file)); + lfi->type = type; + lfi->packed = FALSE; - ei->change->delay_fixed = getFile16BitBE(file); - ei->change->delay_random = getFile16BitBE(file); - ei->change->delay_frames = getFile16BitBE(file); + setString(&lfi->basename, basename); + setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); +} - ei->change->initial_trigger_element= getMappedElement(getFile16BitBE(file)); +static void setLevelFileInfo_PackedLevelFilename(struct LevelFileInfo *lfi, + int type) +{ + lfi->type = type; + lfi->packed = TRUE; - ei->change->explode = getFile8Bit(file); - ei->change->use_target_content = getFile8Bit(file); - ei->change->only_if_complete = getFile8Bit(file); - ei->change->use_random_replace = getFile8Bit(file); + setString(&lfi->basename, getPackedLevelBasename(lfi->type)); + setString(&lfi->filename, getLevelFilenameFromBasename(lfi->basename)); +} - ei->change->random_percentage = getFile8Bit(file); - ei->change->replace_when = getFile8Bit(file); +static int getFiletypeFromID(char *filetype_id) +{ + char *filetype_id_lower; + int filetype = LEVEL_FILE_TYPE_UNKNOWN; + int i; - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - ei->change->target_content.e[x][y] = - getMappedElement(getFile16BitBE(file)); + if (filetype_id == NULL) + return LEVEL_FILE_TYPE_UNKNOWN; - ei->slippery_type = getFile8Bit(file); + filetype_id_lower = getStringToLower(filetype_id); - // some free bytes for future properties and padding - ReadUnusedBytesFromFile(file, LEVEL_CPART_CUS3_UNUSED); + for (i = 0; filetype_id_list[i].id != NULL; i++) + { + char *id_lower = getStringToLower(filetype_id_list[i].id); + + if (strEqual(filetype_id_lower, id_lower)) + filetype = filetype_id_list[i].filetype; - // mark that this custom element has been modified - ei->modified_settings = TRUE; + free(id_lower); + + if (filetype != LEVEL_FILE_TYPE_UNKNOWN) + break; } - level->file_has_custom_elements = TRUE; + free(filetype_id_lower); - return chunk_size; + return filetype; } -static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) +char *getLocalLevelTemplateFilename(void) { - struct ElementInfo *ei; - int chunk_size_expected; - int element; - int i, j, x, y; + return getDefaultLevelFilename(-1); +} - // ---------- custom element base property values (96 bytes) ---------------- +char *getGlobalLevelTemplateFilename(void) +{ + // global variable "leveldir_current" must be modified in the loop below + LevelDirTree *leveldir_current_last = leveldir_current; + char *filename = NULL; - element = getMappedElement(getFile16BitBE(file)); + // check for template level in path from current to topmost tree node - if (!IS_CUSTOM_ELEMENT(element)) + while (leveldir_current != NULL) { - Warn("invalid custom element number %d", element); + filename = getDefaultLevelFilename(-1); - ReadUnusedBytesFromFile(file, chunk_size - 2); + if (fileExists(filename)) + break; - return chunk_size; + leveldir_current = leveldir_current->node_parent; } - ei = &element_info[element]; + // restore global variable "leveldir_current" modified in above loop + leveldir_current = leveldir_current_last; - for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) - ei->description[i] = getFile8Bit(file); - ei->description[MAX_ELEMENT_NAME_LEN] = 0; + return filename; +} - ei->properties[EP_BITFIELD_BASE_NR] = getFile32BitBE(file); +static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi) +{ + int nr = lfi->nr; - ReadUnusedBytesFromFile(file, 4); // reserved for more base properties + // special case: level number is negative => check for level template file + if (nr < 0) + { + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, + getSingleLevelBasename(-1)); - ei->num_change_pages = getFile8Bit(file); + // replace local level template filename with global template filename + setString(&lfi->filename, getGlobalLevelTemplateFilename()); - chunk_size_expected = LEVEL_CHUNK_CUS4_SIZE(ei->num_change_pages); - if (chunk_size_expected != chunk_size) + // no fallback if template file not existing + return; + } + + // special case: check for file name/pattern specified in "levelinfo.conf" + if (leveldir_current->level_filename != NULL) { - ReadUnusedBytesFromFile(file, chunk_size - 43); - return chunk_size_expected; + int filetype = getFiletypeFromID(leveldir_current->level_filetype); + + setLevelFileInfo_FormatLevelFilename(lfi, filetype, + leveldir_current->level_filename, nr); + + lfi->packed = checkForPackageFromBasename(leveldir_current->level_filename); + + if (fileExists(lfi->filename)) + return; + } + else if (leveldir_current->level_filetype != NULL) + { + int filetype = getFiletypeFromID(leveldir_current->level_filetype); + + // check for specified native level file with standard file name + setLevelFileInfo_FormatLevelFilename(lfi, filetype, + "%03d.%s", nr, LEVELFILE_EXTENSION); + if (fileExists(lfi->filename)) + return; } - ei->ce_value_fixed_initial = getFile16BitBE(file); - ei->ce_value_random_initial = getFile16BitBE(file); - ei->use_last_ce_value = getFile8Bit(file); + // check for native Rocks'n'Diamonds level file + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, + "%03d.%s", nr, LEVELFILE_EXTENSION); + if (fileExists(lfi->filename)) + return; + + // check for native Boulder Dash level file + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_BD, "%03d.bd", nr); + if (fileExists(lfi->filename)) + return; + + // check for Emerald Mine level file (V1) + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "a%c%c", + 'a' + (nr / 10) % 26, '0' + nr % 10); + if (fileExists(lfi->filename)) + return; + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "A%c%c", + 'A' + (nr / 10) % 26, '0' + nr % 10); + if (fileExists(lfi->filename)) + return; + + // check for Emerald Mine level file (V2 to V5) + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%d", nr); + if (fileExists(lfi->filename)) + return; - ei->use_gfx_element = getFile8Bit(file); - ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); + // check for Emerald Mine level file (V6 / single mode) + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02ds", nr); + if (fileExists(lfi->filename)) + return; + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dS", nr); + if (fileExists(lfi->filename)) + return; - ei->collect_score_initial = getFile8Bit(file); - ei->collect_count_initial = getFile8Bit(file); + // check for Emerald Mine level file (V6 / teamwork mode) + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dt", nr); + if (fileExists(lfi->filename)) + return; + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dT", nr); + if (fileExists(lfi->filename)) + return; - 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); + // check for various packed level file formats + setLevelFileInfo_PackedLevelFilename(lfi, LEVEL_FILE_TYPE_UNKNOWN); + if (fileExists(lfi->filename)) + return; - // bits 0 - 15 of "move_pattern" ... - ei->move_pattern = getFile16BitBE(file); - ei->move_direction_initial = getFile8Bit(file); - ei->move_stepsize = getFile8Bit(file); + // no known level file found -- use default values (and fail later) + setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND, + "%03d.%s", nr, LEVELFILE_EXTENSION); +} - ei->slippery_type = getFile8Bit(file); +static void determineLevelFileInfo_Filetype(struct LevelFileInfo *lfi) +{ + if (lfi->type == LEVEL_FILE_TYPE_UNKNOWN) + lfi->type = getFileTypeFromBasename(lfi->basename); - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); + if (lfi->type == LEVEL_FILE_TYPE_RND) + lfi->type = getFileTypeFromMagicBytes(lfi->filename, lfi->type); +} - ei->move_enter_element = getMappedElement(getFile16BitBE(file)); - ei->move_leave_element = getMappedElement(getFile16BitBE(file)); - ei->move_leave_type = getFile8Bit(file); +static void setLevelFileInfo(struct LevelFileInfo *level_file_info, int nr) +{ + // always start with reliable default values + setFileInfoToDefaults(level_file_info); - // ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) - ei->move_pattern |= (getFile16BitBE(file) << 16); + level_file_info->nr = nr; // set requested level number - ei->access_direction = getFile8Bit(file); + determineLevelFileInfo_Filename(level_file_info); + determineLevelFileInfo_Filetype(level_file_info); +} - ei->explosion_delay = getFile8Bit(file); - ei->ignition_delay = getFile8Bit(file); - ei->explosion_type = getFile8Bit(file); +static void copyLevelFileInfo(struct LevelFileInfo *lfi_from, + struct LevelFileInfo *lfi_to) +{ + lfi_to->nr = lfi_from->nr; + lfi_to->type = lfi_from->type; + lfi_to->packed = lfi_from->packed; - // some free bytes for future custom property values and padding - ReadUnusedBytesFromFile(file, 1); + setString(&lfi_to->basename, lfi_from->basename); + setString(&lfi_to->filename, lfi_from->filename); +} - // ---------- change page property values (48 bytes) ------------------------ +// ---------------------------------------------------------------------------- +// functions for loading R'n'D level +// ---------------------------------------------------------------------------- - setElementChangePages(ei, ei->num_change_pages); +int getMappedElement(int element) +{ + // remap some (historic, now obsolete) elements - for (i = 0; i < ei->num_change_pages; i++) + switch (element) { - struct ElementChangeInfo *change = &ei->change_page[i]; - unsigned int event_bits; - - // always start with reliable default values - setElementChangeInfoToDefaults(change); + case EL_PLAYER_OBSOLETE: + element = EL_PLAYER_1; + break; - // bits 0 - 31 of "has_event[]" ... - event_bits = getFile32BitBE(file); - for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) - if (event_bits & (1 << j)) - change->has_event[j] = TRUE; + case EL_KEY_OBSOLETE: + element = EL_KEY_1; + break; - change->target_element = getMappedElement(getFile16BitBE(file)); + case EL_EM_KEY_1_FILE_OBSOLETE: + element = EL_EM_KEY_1; + break; - change->delay_fixed = getFile16BitBE(file); - change->delay_random = getFile16BitBE(file); - change->delay_frames = getFile16BitBE(file); + case EL_EM_KEY_2_FILE_OBSOLETE: + element = EL_EM_KEY_2; + break; - change->initial_trigger_element = getMappedElement(getFile16BitBE(file)); + case EL_EM_KEY_3_FILE_OBSOLETE: + element = EL_EM_KEY_3; + break; - change->explode = getFile8Bit(file); - change->use_target_content = getFile8Bit(file); - change->only_if_complete = getFile8Bit(file); - change->use_random_replace = getFile8Bit(file); + case EL_EM_KEY_4_FILE_OBSOLETE: + element = EL_EM_KEY_4; + break; - change->random_percentage = getFile8Bit(file); - change->replace_when = getFile8Bit(file); + case EL_ENVELOPE_OBSOLETE: + element = EL_ENVELOPE_1; + break; - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - change->target_content.e[x][y]= getMappedElement(getFile16BitBE(file)); + case EL_SP_EMPTY: + element = EL_EMPTY; + break; - change->can_change = getFile8Bit(file); + default: + if (element >= NUM_FILE_ELEMENTS) + { + Warn("invalid level element %d", element); - change->trigger_side = getFile8Bit(file); + element = EL_UNKNOWN; + } + break; + } - change->trigger_player = getFile8Bit(file); - change->trigger_page = getFile8Bit(file); + return element; +} - change->trigger_page = (change->trigger_page == CH_PAGE_ANY_FILE ? - CH_PAGE_ANY : (1 << change->trigger_page)); +static int getMappedElementByVersion(int element, int game_version) +{ + // remap some elements due to certain game version - change->has_action = getFile8Bit(file); - change->action_type = getFile8Bit(file); - change->action_mode = getFile8Bit(file); - change->action_arg = getFile16BitBE(file); + 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); + } - // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) - event_bits = getFile8Bit(file); - for (j = 32; j < NUM_CHANGE_EVENTS; j++) - if (event_bits & (1 << (j - 32))) - change->has_event[j] = TRUE; + 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); } - // mark this custom element as modified - ei->modified_settings = TRUE; + return element; +} - level->file_has_custom_elements = TRUE; +static int LoadLevel_VERS(File *file, int chunk_size, struct LevelInfo *level) +{ + level->file_version = getFileVersion(file); + level->game_version = getFileVersion(file); return chunk_size; } -static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_DATE(File *file, int chunk_size, struct LevelInfo *level) { - struct ElementInfo *ei; - struct ElementGroupInfo *group; - int element; - int i; + level->creation_date.year = getFile16BitBE(file); + level->creation_date.month = getFile8Bit(file); + level->creation_date.day = getFile8Bit(file); - element = getMappedElement(getFile16BitBE(file)); + level->creation_date.src = DATE_SRC_LEVELFILE; - if (!IS_GROUP_ELEMENT(element)) - { - Warn("invalid group element number %d", element); + return chunk_size; +} - ReadUnusedBytesFromFile(file, chunk_size - 2); +static int LoadLevel_HEAD(File *file, int chunk_size, struct LevelInfo *level) +{ + int initial_player_stepsize; + int initial_player_gravity; + int i, x, y; - return chunk_size; - } + level->fieldx = getFile8Bit(file); + level->fieldy = getFile8Bit(file); - ei = &element_info[element]; + level->time = getFile16BitBE(file); + level->gems_needed = getFile16BitBE(file); - for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) - ei->description[i] = getFile8Bit(file); - ei->description[MAX_ELEMENT_NAME_LEN] = 0; + for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) + level->name[i] = getFile8Bit(file); + level->name[MAX_LEVEL_NAME_LEN] = 0; - group = element_info[element].group; + for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) + level->score[i] = getFile8Bit(file); - group->num_elements = getFile8Bit(file); + level->num_yamyam_contents = STD_ELEMENT_CONTENTS; + for (i = 0; i < STD_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + level->yamyam_content[i].e[x][y] = getMappedElement(getFile8Bit(file)); + + level->amoeba_speed = getFile8Bit(file); + level->time_magic_wall = getFile8Bit(file); + level->time_wheel = getFile8Bit(file); + level->amoeba_content = getMappedElement(getFile8Bit(file)); + + initial_player_stepsize = (getFile8Bit(file) == 1 ? STEPSIZE_FAST : + STEPSIZE_NORMAL); + + for (i = 0; i < MAX_PLAYERS; i++) + level->initial_player_stepsize[i] = initial_player_stepsize; + + initial_player_gravity = (getFile8Bit(file) == 1 ? TRUE : FALSE); + + for (i = 0; i < MAX_PLAYERS; i++) + level->initial_player_gravity[i] = initial_player_gravity; + + level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->em_slippery_gems = (getFile8Bit(file) == 1 ? TRUE : FALSE); - ei->use_gfx_element = getFile8Bit(file); - ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); + level->use_custom_template = (getFile8Bit(file) == 1 ? TRUE : FALSE); - group->choice_mode = getFile8Bit(file); + 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); - // some free bytes for future values and padding - ReadUnusedBytesFromFile(file, 3); + level->use_spring_bug = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->use_step_counter = (getFile8Bit(file) == 1 ? TRUE : FALSE); - for (i = 0; i < MAX_ELEMENTS_IN_GROUP; i++) - group->element[i] = getMappedElement(getFile16BitBE(file)); + 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); - // mark this group element as modified - element_info[element].modified_settings = TRUE; + level->game_engine_type = getFile8Bit(file); - level->file_has_custom_elements = TRUE; + ReadUnusedBytesFromFile(file, LEVEL_CHUNK_HEAD_UNUSED); return chunk_size; } -static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, - int element, int real_element) +static int LoadLevel_NAME(File *file, int chunk_size, struct LevelInfo *level) { - int micro_chunk_size = 0; - int conf_type = getFile8Bit(file); - int byte_mask = conf_type & CONF_MASK_BYTES; - boolean element_found = FALSE; int i; - micro_chunk_size += 1; + for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) + level->name[i] = getFile8Bit(file); + level->name[MAX_LEVEL_NAME_LEN] = 0; - if (byte_mask == CONF_MASK_MULTI_BYTES) - { - int num_bytes = getFile16BitBE(file); - byte *buffer = checked_malloc(num_bytes); + return chunk_size; +} - ReadBytesFromFile(file, buffer, num_bytes); +static int LoadLevel_AUTH(File *file, int chunk_size, struct LevelInfo *level) +{ + int i; - for (i = 0; conf[i].data_type != -1; i++) - { - if (conf[i].element == element && - conf[i].conf_type == conf_type) - { - int data_type = conf[i].data_type; - int num_entities = num_bytes / CONF_ENTITY_NUM_BYTES(data_type); - int max_num_entities = conf[i].max_num_entities; + for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) + level->author[i] = getFile8Bit(file); + level->author[MAX_LEVEL_AUTHOR_LEN] = 0; - if (num_entities > max_num_entities) - { - Warn("truncating number of entities for element %d from %d to %d", - element, num_entities, max_num_entities); + return chunk_size; +} - num_entities = max_num_entities; - } +static int LoadLevel_BODY(File *file, int chunk_size, struct LevelInfo *level) +{ + int x, y; + int chunk_size_expected = level->fieldx * level->fieldy; - if (num_entities == 0 && (data_type == TYPE_ELEMENT_LIST || - data_type == TYPE_CONTENT_LIST)) - { - // for element and content lists, zero entities are not allowed - Warn("found empty list of entities for element %d", element); + /* Note: "chunk_size" was wrong before version 2.0 when elements are + stored with 16-bit encoding (and should be twice as big then). + Even worse, playfield data was stored 16-bit when only yamyam content + contained 16-bit elements and vice versa. */ - // do not set "num_entities" here to prevent reading behind buffer + if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) + chunk_size_expected *= 2; - *(int *)(conf[i].num_entities) = 1; // at least one is required - } - else - { - *(int *)(conf[i].num_entities) = num_entities; - } + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size); + return chunk_size_expected; + } - element_found = TRUE; + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + level->field[x][y] = + getMappedElement(level->encoding_16bit_field ? getFile16BitBE(file) : + getFile8Bit(file)); + return chunk_size; +} - if (data_type == TYPE_STRING) - { - char *string = (char *)(conf[i].value); - int j; +static int LoadLevel_CONT(File *file, int chunk_size, struct LevelInfo *level) +{ + int i, x, y; + int header_size = 4; + int content_size = MAX_ELEMENT_CONTENTS * 3 * 3; + int chunk_size_expected = header_size + content_size; - for (j = 0; j < max_num_entities; j++) - string[j] = (j < num_entities ? buffer[j] : '\0'); - } - else if (data_type == TYPE_ELEMENT_LIST) - { - int *element_array = (int *)(conf[i].value); - int j; + /* Note: "chunk_size" was wrong before version 2.0 when elements are + stored with 16-bit encoding (and should be twice as big then). + Even worse, playfield data was stored 16-bit when only yamyam content + contained 16-bit elements and vice versa. */ - for (j = 0; j < num_entities; j++) - element_array[j] = - getMappedElement(CONF_ELEMENTS_ELEMENT(buffer, j)); - } - else if (data_type == TYPE_CONTENT_LIST) - { - struct Content *content= (struct Content *)(conf[i].value); - int c, x, y; + if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) + chunk_size_expected += content_size; - for (c = 0; c < num_entities; c++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content[c].e[x][y] = - getMappedElement(CONF_CONTENTS_ELEMENT(buffer, c, x, y)); - } - else - element_found = FALSE; + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size); + return chunk_size_expected; + } - break; - } - } + getFile8Bit(file); + level->num_yamyam_contents = getFile8Bit(file); + getFile8Bit(file); + getFile8Bit(file); - checked_free(buffer); + // correct invalid number of content fields -- should never happen + if (level->num_yamyam_contents < 1 || + level->num_yamyam_contents > MAX_ELEMENT_CONTENTS) + level->num_yamyam_contents = STD_ELEMENT_CONTENTS; - micro_chunk_size += 2 + num_bytes; - } - else // constant size configuration data (1, 2 or 4 bytes) - { - int value = (byte_mask == CONF_MASK_1_BYTE ? getFile8Bit (file) : - byte_mask == CONF_MASK_2_BYTE ? getFile16BitBE(file) : - byte_mask == CONF_MASK_4_BYTE ? getFile32BitBE(file) : 0); + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + level->yamyam_content[i].e[x][y] = + getMappedElement(level->encoding_16bit_field ? + getFile16BitBE(file) : getFile8Bit(file)); + return chunk_size; +} - for (i = 0; conf[i].data_type != -1; i++) - { - if (conf[i].element == element && - conf[i].conf_type == conf_type) - { - int data_type = conf[i].data_type; +static int LoadLevel_CNT2(File *file, int chunk_size, struct LevelInfo *level) +{ + int i, x, y; + int element; + int num_contents; + int content_array[MAX_ELEMENT_CONTENTS][3][3]; - if (data_type == TYPE_ELEMENT) - value = getMappedElement(value); + element = getMappedElement(getFile16BitBE(file)); + num_contents = getFile8Bit(file); - if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value) = (value ? TRUE : FALSE); - else - *(int *) (conf[i].value) = value; + getFile8Bit(file); // content x size (unused) + getFile8Bit(file); // content y size (unused) - element_found = TRUE; + ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED); - break; - } - } + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content_array[i][x][y] = getMappedElement(getFile16BitBE(file)); - micro_chunk_size += CONF_VALUE_NUM_BYTES(byte_mask); - } + // correct invalid number of content fields -- should never happen + if (num_contents < 1 || num_contents > MAX_ELEMENT_CONTENTS) + num_contents = STD_ELEMENT_CONTENTS; - if (!element_found) + if (element == EL_YAMYAM) { - char *error_conf_chunk_bytes = - (byte_mask == CONF_MASK_1_BYTE ? "CONF_VALUE_8_BIT" : - byte_mask == CONF_MASK_2_BYTE ? "CONF_VALUE_16_BIT" : - byte_mask == CONF_MASK_4_BYTE ? "CONF_VALUE_32_BIT" :"CONF_VALUE_BYTES"); - int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN; - int error_element = real_element; + level->num_yamyam_contents = num_contents; - Warn("cannot load micro chunk '%s(%d)' value for element %d ['%s']", - error_conf_chunk_bytes, error_conf_chunk_token, - error_element, EL_NAME(error_element)); + for (i = 0; i < num_contents; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + level->yamyam_content[i].e[x][y] = content_array[i][x][y]; + } + else if (element == EL_BD_AMOEBA) + { + level->amoeba_content = content_array[0][0][0]; + } + else + { + Warn("cannot load content for element '%d'", element); } - return micro_chunk_size; + return chunk_size; } -static int LoadLevel_INFO(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CNT3(File *file, int chunk_size, struct LevelInfo *level) { - int real_chunk_size = 0; + int i; + int element; + int envelope_nr; + int envelope_len; + int chunk_size_expected; - li = *level; // copy level data into temporary buffer + element = getMappedElement(getFile16BitBE(file)); + if (!IS_ENVELOPE(element)) + element = EL_ENVELOPE_1; - while (!checkEndOfFile(file)) - { - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_INFO, -1, -1); + envelope_nr = element - EL_ENVELOPE_1; - if (real_chunk_size >= chunk_size) - break; + envelope_len = getFile16BitBE(file); + + level->envelope[envelope_nr].xsize = getFile8Bit(file); + level->envelope[envelope_nr].ysize = getFile8Bit(file); + + ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT3_UNUSED); + + chunk_size_expected = LEVEL_CHUNK_CNT3_SIZE(envelope_len); + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - LEVEL_CHUNK_CNT3_HEADER); + return chunk_size_expected; } - *level = li; // copy temporary buffer back to level data + for (i = 0; i < envelope_len; i++) + level->envelope[envelope_nr].text[i] = getFile8Bit(file); - return real_chunk_size; + return chunk_size; } -static int LoadLevel_CONF(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level) { - int real_chunk_size = 0; + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = 2 + num_changed_custom_elements * 6; + int i; - li = *level; // copy level data into temporary buffer + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; + } - while (!checkEndOfFile(file)) + for (i = 0; i < num_changed_custom_elements; i++) { int element = getMappedElement(getFile16BitBE(file)); + int properties = getFile32BitBE(file); - real_chunk_size += 2; - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CONF, - element, element); - if (real_chunk_size >= chunk_size) - break; + if (IS_CUSTOM_ELEMENT(element)) + element_info[element].properties[EP_BITFIELD_BASE_NR] = properties; + else + Warn("invalid custom element number %d", element); + + // older game versions that wrote level files with CUS1 chunks used + // different default push delay values (not yet stored in level file) + element_info[element].push_delay_fixed = 2; + element_info[element].push_delay_random = 8; } - *level = li; // copy temporary buffer back to level data + level->file_has_custom_elements = TRUE; - return real_chunk_size; + return chunk_size; } -static int LoadLevel_ELEM(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level) { - int real_chunk_size = 0; + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = 2 + num_changed_custom_elements * 4; + int i; - li = *level; // copy level data into temporary buffer + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; + } - while (!checkEndOfFile(file)) + for (i = 0; i < num_changed_custom_elements; i++) { int element = getMappedElement(getFile16BitBE(file)); + int custom_target_element = getMappedElement(getFile16BitBE(file)); - real_chunk_size += 2; - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_ELEM, - element, element); - if (real_chunk_size >= chunk_size) - break; + if (IS_CUSTOM_ELEMENT(element)) + element_info[element].change->target_element = custom_target_element; + else + Warn("invalid custom element number %d", element); } - *level = li; // copy temporary buffer back to level data + level->file_has_custom_elements = TRUE; - return real_chunk_size; + return chunk_size; } -static int LoadLevel_NOTE(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) { - int element = getMappedElement(getFile16BitBE(file)); - int envelope_nr = element - EL_ENVELOPE_1; - int real_chunk_size = 2; - - xx_envelope = level->envelope[envelope_nr]; // copy into temporary buffer + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = LEVEL_CHUNK_CUS3_SIZE(num_changed_custom_elements); + int i, j, x, y; - while (!checkEndOfFile(file)) + if (chunk_size_expected != chunk_size) { - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_NOTE, - -1, element); - - if (real_chunk_size >= chunk_size) - break; + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; } - level->envelope[envelope_nr] = xx_envelope; // copy from temporary buffer + for (i = 0; i < num_changed_custom_elements; i++) + { + int element = getMappedElement(getFile16BitBE(file)); + struct ElementInfo *ei = &element_info[element]; + unsigned int event_bits; - return real_chunk_size; -} + if (!IS_CUSTOM_ELEMENT(element)) + { + Warn("invalid custom element number %d", element); -static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) -{ - int element = getMappedElement(getFile16BitBE(file)); - int real_chunk_size = 2; - struct ElementInfo *ei = &element_info[element]; - int i; + element = EL_INTERNAL_DUMMY; + } - xx_ei = *ei; // copy element data into temporary buffer + for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++) + ei->description[j] = getFile8Bit(file); + ei->description[MAX_ELEMENT_NAME_LEN] = 0; - xx_ei.num_change_pages = -1; + ei->properties[EP_BITFIELD_BASE_NR] = getFile32BitBE(file); - while (!checkEndOfFile(file)) - { - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CUSX_base, - -1, element); - if (xx_ei.num_change_pages != -1) - break; + // some free bytes for future properties and padding + ReadUnusedBytesFromFile(file, 7); - if (real_chunk_size >= chunk_size) - break; - } + ei->use_gfx_element = getFile8Bit(file); + ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); - *ei = xx_ei; + ei->collect_score_initial = getFile8Bit(file); + ei->collect_count_initial = getFile8Bit(file); - if (ei->num_change_pages == -1) - { - Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", - EL_NAME(element)); + ei->push_delay_fixed = getFile16BitBE(file); + ei->push_delay_random = getFile16BitBE(file); + ei->move_delay_fixed = getFile16BitBE(file); + ei->move_delay_random = getFile16BitBE(file); - ei->num_change_pages = 1; + ei->move_pattern = getFile16BitBE(file); + ei->move_direction_initial = getFile8Bit(file); + ei->move_stepsize = getFile8Bit(file); - setElementChangePages(ei, 1); - setElementChangeInfoToDefaults(ei->change); + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); - return real_chunk_size; - } + // bits 0 - 31 of "has_event[]" + event_bits = getFile32BitBE(file); + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) + if (event_bits & (1u << j)) + ei->change->has_event[j] = TRUE; - // initialize number of change pages stored for this custom element - setElementChangePages(ei, ei->num_change_pages); - for (i = 0; i < ei->num_change_pages; i++) - setElementChangeInfoToDefaults(&ei->change_page[i]); + ei->change->target_element = getMappedElement(getFile16BitBE(file)); - // start with reading properties for the first change page - xx_current_change_page = 0; + ei->change->delay_fixed = getFile16BitBE(file); + ei->change->delay_random = getFile16BitBE(file); + ei->change->delay_frames = getFile16BitBE(file); - while (!checkEndOfFile(file)) - { - struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page]; + ei->change->initial_trigger_element = getMappedElement(getFile16BitBE(file)); - xx_change = *change; // copy change data into temporary buffer + ei->change->explode = getFile8Bit(file); + ei->change->use_target_content = getFile8Bit(file); + ei->change->only_if_complete = getFile8Bit(file); + ei->change->use_random_replace = getFile8Bit(file); - resetEventBits(); // reset bits; change page might have changed + ei->change->random_percentage = getFile8Bit(file); + ei->change->replace_when = getFile8Bit(file); - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CUSX_change, - -1, element); + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + ei->change->target_content.e[x][y] = + getMappedElement(getFile16BitBE(file)); - *change = xx_change; + ei->slippery_type = getFile8Bit(file); - setEventFlagsFromEventBits(change); + // some free bytes for future properties and padding + ReadUnusedBytesFromFile(file, LEVEL_CPART_CUS3_UNUSED); - if (real_chunk_size >= chunk_size) - break; + // mark that this custom element has been modified + ei->modified_settings = TRUE; } level->file_has_custom_elements = TRUE; - return real_chunk_size; + return chunk_size; } -static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) { - int element = getMappedElement(getFile16BitBE(file)); - int real_chunk_size = 2; - struct ElementInfo *ei = &element_info[element]; - struct ElementGroupInfo *group = ei->group; - - xx_ei = *ei; // copy element data into temporary buffer - xx_group = *group; // copy group data into temporary buffer - - while (!checkEndOfFile(file)) - { - real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_GRPX, - -1, element); - - if (real_chunk_size >= chunk_size) - break; - } - - *ei = xx_ei; - *group = xx_group; - - level->file_has_custom_elements = TRUE; + struct ElementInfo *ei; + int chunk_size_expected; + int element; + int i, j, x, y; - return real_chunk_size; -} + // ---------- custom element base property values (96 bytes) ---------------- -static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - char *filename = level_file_info->filename; - char cookie[MAX_LINE_LEN]; - char chunk_name[CHUNK_ID_LEN + 1]; - int chunk_size; - File *file; + element = getMappedElement(getFile16BitBE(file)); - if (!(file = openFile(filename, MODE_READ))) + if (!IS_CUSTOM_ELEMENT(element)) { - level->no_valid_file = TRUE; - level->no_level_file = TRUE; + Warn("invalid custom element number %d", element); - if (level_info_only) - return; + ReadUnusedBytesFromFile(file, chunk_size - 2); - Warn("cannot read level '%s' -- using empty level", filename); + return chunk_size; + } - if (!setup.editor.use_template_for_new_levels) - return; + ei = &element_info[element]; - // if level file not found, try to initialize level data from template - filename = getGlobalLevelTemplateFilename(); + for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) + ei->description[i] = getFile8Bit(file); + ei->description[MAX_ELEMENT_NAME_LEN] = 0; - if (!(file = openFile(filename, MODE_READ))) - return; + ei->properties[EP_BITFIELD_BASE_NR] = getFile32BitBE(file); - // default: for empty levels, use level template for custom elements - level->use_custom_template = TRUE; + ReadUnusedBytesFromFile(file, 4); // reserved for more base properties - level->no_valid_file = FALSE; - } + ei->num_change_pages = getFile8Bit(file); - getFileChunkBE(file, chunk_name, NULL); - if (strEqual(chunk_name, "RND1")) + chunk_size_expected = LEVEL_CHUNK_CUS4_SIZE(ei->num_change_pages); + if (chunk_size_expected != chunk_size) { - getFile32BitBE(file); // not used + ReadUnusedBytesFromFile(file, chunk_size - 43); + return chunk_size_expected; + } - getFileChunkBE(file, chunk_name, NULL); - if (!strEqual(chunk_name, "CAVE")) - { - level->no_valid_file = TRUE; + ei->ce_value_fixed_initial = getFile16BitBE(file); + ei->ce_value_random_initial = getFile16BitBE(file); + ei->use_last_ce_value = getFile8Bit(file); - Warn("unknown format of level file '%s'", filename); + ei->use_gfx_element = getFile8Bit(file); + ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); - closeFile(file); + ei->collect_score_initial = getFile8Bit(file); + ei->collect_count_initial = getFile8Bit(file); - return; - } - } - else // check for pre-2.0 file format with cookie string - { - strcpy(cookie, chunk_name); - if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) - cookie[4] = '\0'; - if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') - cookie[strlen(cookie) - 1] = '\0'; + 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); - if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL)) - { - level->no_valid_file = TRUE; + // bits 0 - 15 of "move_pattern" ... + ei->move_pattern = getFile16BitBE(file); + ei->move_direction_initial = getFile8Bit(file); + ei->move_stepsize = getFile8Bit(file); - Warn("unknown format of level file '%s'", filename); + ei->slippery_type = getFile8Bit(file); - closeFile(file); + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); - return; - } + ei->move_enter_element = getMappedElement(getFile16BitBE(file)); + ei->move_leave_element = getMappedElement(getFile16BitBE(file)); + ei->move_leave_type = getFile8Bit(file); - if ((level->file_version = getFileVersionFromCookieString(cookie)) == -1) - { - level->no_valid_file = TRUE; + // ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) + ei->move_pattern |= (getFile16BitBE(file) << 16); - Warn("unsupported version of level file '%s'", filename); + ei->access_direction = getFile8Bit(file); - closeFile(file); + ei->explosion_delay = getFile8Bit(file); + ei->ignition_delay = getFile8Bit(file); + ei->explosion_type = getFile8Bit(file); - return; - } + // some free bytes for future custom property values and padding + ReadUnusedBytesFromFile(file, 1); - // pre-2.0 level files have no game version, so use file version here - level->game_version = level->file_version; - } + // ---------- change page property values (48 bytes) ------------------------ - if (level->file_version < FILE_VERSION_1_2) - { - // level files from versions before 1.2.0 without chunk structure - LoadLevel_HEAD(file, LEVEL_CHUNK_HEAD_SIZE, level); - LoadLevel_BODY(file, level->fieldx * level->fieldy, level); - } - else + setElementChangePages(ei, ei->num_change_pages); + + for (i = 0; i < ei->num_change_pages; i++) { - static struct - { - char *name; - int size; - int (*loader)(File *, int, struct LevelInfo *); - } - chunk_info[] = - { - { "VERS", LEVEL_CHUNK_VERS_SIZE, LoadLevel_VERS }, - { "DATE", LEVEL_CHUNK_DATE_SIZE, LoadLevel_DATE }, - { "HEAD", LEVEL_CHUNK_HEAD_SIZE, LoadLevel_HEAD }, - { "NAME", LEVEL_CHUNK_NAME_SIZE, LoadLevel_NAME }, - { "AUTH", LEVEL_CHUNK_AUTH_SIZE, LoadLevel_AUTH }, - { "INFO", -1, LoadLevel_INFO }, - { "BODY", -1, LoadLevel_BODY }, - { "CONT", -1, LoadLevel_CONT }, - { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 }, - { "CNT3", -1, LoadLevel_CNT3 }, - { "CUS1", -1, LoadLevel_CUS1 }, - { "CUS2", -1, LoadLevel_CUS2 }, - { "CUS3", -1, LoadLevel_CUS3 }, - { "CUS4", -1, LoadLevel_CUS4 }, - { "GRP1", -1, LoadLevel_GRP1 }, - { "CONF", -1, LoadLevel_CONF }, - { "ELEM", -1, LoadLevel_ELEM }, - { "NOTE", -1, LoadLevel_NOTE }, - { "CUSX", -1, LoadLevel_CUSX }, - { "GRPX", -1, LoadLevel_GRPX }, + struct ElementChangeInfo *change = &ei->change_page[i]; + unsigned int event_bits; - { NULL, 0, NULL } - }; + // always start with reliable default values + setElementChangeInfoToDefaults(change); - while (getFileChunkBE(file, chunk_name, &chunk_size)) - { - int i = 0; + // bits 0 - 31 of "has_event[]" ... + event_bits = getFile32BitBE(file); + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) + if (event_bits & (1u << j)) + change->has_event[j] = TRUE; - while (chunk_info[i].name != NULL && - !strEqual(chunk_name, chunk_info[i].name)) - i++; + change->target_element = getMappedElement(getFile16BitBE(file)); - if (chunk_info[i].name == NULL) - { - Warn("unknown chunk '%s' in level file '%s'", - chunk_name, filename); + change->delay_fixed = getFile16BitBE(file); + change->delay_random = getFile16BitBE(file); + change->delay_frames = getFile16BitBE(file); - ReadUnusedBytesFromFile(file, chunk_size); - } - else if (chunk_info[i].size != -1 && - chunk_info[i].size != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); + change->initial_trigger_element = getMappedElement(getFile16BitBE(file)); - ReadUnusedBytesFromFile(file, chunk_size); - } - else - { - // call function to load this level chunk - int chunk_size_expected = - (chunk_info[i].loader)(file, chunk_size, level); + change->explode = getFile8Bit(file); + change->use_target_content = getFile8Bit(file); + change->only_if_complete = getFile8Bit(file); + change->use_random_replace = getFile8Bit(file); - // the size of some chunks cannot be checked before reading other - // chunks first (like "HEAD" and "BODY") that contain some header - // information, so check them here - if (chunk_size_expected != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); - } - } - } - } + change->random_percentage = getFile8Bit(file); + change->replace_when = getFile8Bit(file); - closeFile(file); -} + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + change->target_content.e[x][y]= getMappedElement(getFile16BitBE(file)); + change->can_change = getFile8Bit(file); -// ---------------------------------------------------------------------------- -// functions for loading EM level -// ---------------------------------------------------------------------------- + change->trigger_side = getFile8Bit(file); -static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) -{ - static int ball_xy[8][2] = - { - { 0, 0 }, - { 1, 0 }, - { 2, 0 }, - { 0, 1 }, - { 2, 1 }, - { 0, 2 }, - { 1, 2 }, - { 2, 2 }, - }; - struct LevelInfo_EM *level_em = level->native_em_level; - struct CAVE *cav = level_em->cav; - int i, j, x, y; + change->trigger_player = getFile8Bit(file); + change->trigger_page = getFile8Bit(file); - cav->width = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH); - cav->height = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT); + change->trigger_page = (change->trigger_page == CH_PAGE_ANY_FILE ? + CH_PAGE_ANY : (1 << change->trigger_page)); - cav->time_seconds = level->time; - cav->gems_needed = level->gems_needed; + change->has_action = getFile8Bit(file); + change->action_type = getFile8Bit(file); + change->action_mode = getFile8Bit(file); + change->action_arg = getFile16BitBE(file); - cav->emerald_score = level->score[SC_EMERALD]; - cav->diamond_score = level->score[SC_DIAMOND]; - cav->alien_score = level->score[SC_ROBOT]; - cav->tank_score = level->score[SC_SPACESHIP]; - cav->bug_score = level->score[SC_BUG]; - cav->eater_score = level->score[SC_YAMYAM]; - cav->nut_score = level->score[SC_NUT]; - cav->dynamite_score = level->score[SC_DYNAMITE]; - cav->key_score = level->score[SC_KEY]; - cav->exit_score = level->score[SC_TIME_BONUS]; + // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) + event_bits = getFile8Bit(file); + for (j = 32; j < NUM_CHANGE_EVENTS; j++) + if (event_bits & (1u << (j - 32))) + change->has_event[j] = TRUE; + } - cav->num_eater_arrays = level->num_yamyam_contents; + // mark this custom element as modified + ei->modified_settings = TRUE; - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - cav->eater_array[i][y * 3 + x] = - map_element_RND_to_EM_cave(level->yamyam_content[i].e[x][y]); + level->file_has_custom_elements = TRUE; - cav->amoeba_time = level->amoeba_speed; - cav->wonderwall_time = level->time_magic_wall; - cav->wheel_time = level->time_wheel; + return chunk_size; +} - cav->android_move_time = level->android_move_time; - cav->android_clone_time = level->android_clone_time; - cav->ball_random = level->ball_random; - cav->ball_active = level->ball_active_initial; - cav->ball_time = level->ball_time; - cav->num_ball_arrays = level->num_ball_contents; +static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level) +{ + struct ElementInfo *ei; + struct ElementGroupInfo *group; + int element; + int i; + + element = getMappedElement(getFile16BitBE(file)); + + if (!IS_GROUP_ELEMENT(element)) + { + Warn("invalid group element number %d", element); - cav->lenses_score = level->lenses_score; - cav->magnify_score = level->magnify_score; - cav->slurp_score = level->slurp_score; + ReadUnusedBytesFromFile(file, chunk_size - 2); - cav->lenses_time = level->lenses_time; - cav->magnify_time = level->magnify_time; + return chunk_size; + } - cav->wind_direction = - map_direction_RND_to_EM(level->wind_direction_initial); + ei = &element_info[element]; - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (j = 0; j < 8; j++) - cav->ball_array[i][j] = - map_element_RND_to_EM_cave(level->ball_content[i]. - e[ball_xy[j][0]][ball_xy[j][1]]); + for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) + ei->description[i] = getFile8Bit(file); + ei->description[MAX_ELEMENT_NAME_LEN] = 0; - map_android_clone_elements_RND_to_EM(level); + group = element_info[element].group; - // first fill the complete playfield with the empty space element - for (y = 0; y < EM_MAX_CAVE_HEIGHT; y++) - for (x = 0; x < EM_MAX_CAVE_WIDTH; x++) - cav->cave[x][y] = Cblank; + group->num_elements = getFile8Bit(file); - // then copy the real level contents from level file into the playfield - for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) - { - int new_element = map_element_RND_to_EM_cave(level->field[x][y]); + ei->use_gfx_element = getFile8Bit(file); + ei->gfx_element_initial = getMappedElement(getFile16BitBE(file)); - if (level->field[x][y] == EL_AMOEBA_DEAD) - new_element = map_element_RND_to_EM_cave(EL_AMOEBA_WET); + group->choice_mode = getFile8Bit(file); - cav->cave[x][y] = new_element; - } + // some free bytes for future values and padding + ReadUnusedBytesFromFile(file, 3); - for (i = 0; i < MAX_PLAYERS; i++) - { - cav->player_x[i] = -1; - cav->player_y[i] = -1; - } + for (i = 0; i < MAX_ELEMENTS_IN_GROUP; i++) + group->element[i] = getMappedElement(getFile16BitBE(file)); - // initialize player positions and delete players from the playfield - for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) - { - if (IS_PLAYER_ELEMENT(level->field[x][y])) - { - int player_nr = GET_PLAYER_NR(level->field[x][y]); + // mark this group element as modified + element_info[element].modified_settings = TRUE; - cav->player_x[player_nr] = x; - cav->player_y[player_nr] = y; + level->file_has_custom_elements = TRUE; - cav->cave[x][y] = map_element_RND_to_EM_cave(EL_EMPTY); - } - } + return chunk_size; } -static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) +static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, + int element, int real_element) { - static int ball_xy[8][2] = + int micro_chunk_size = 0; + int conf_type = getFile8Bit(file); + int byte_mask = conf_type & CONF_MASK_BYTES; + boolean element_found = FALSE; + int i; + + micro_chunk_size += 1; + + if (byte_mask == CONF_MASK_MULTI_BYTES) { - { 0, 0 }, - { 1, 0 }, - { 2, 0 }, - { 0, 1 }, - { 2, 1 }, - { 0, 2 }, - { 1, 2 }, - { 2, 2 }, - }; - struct LevelInfo_EM *level_em = level->native_em_level; - struct CAVE *cav = level_em->cav; - int i, j, x, y; + int num_bytes = getFile16BitBE(file); + byte *buffer = checked_malloc(num_bytes); - level->fieldx = MIN(cav->width, MAX_LEV_FIELDX); - level->fieldy = MIN(cav->height, MAX_LEV_FIELDY); + ReadBytesFromFile(file, buffer, num_bytes); - level->time = cav->time_seconds; - level->gems_needed = cav->gems_needed; + for (i = 0; conf[i].data_type != -1; i++) + { + if (conf[i].element == element && + conf[i].conf_type == conf_type) + { + int data_type = conf[i].data_type; + int num_entities = num_bytes / CONF_ENTITY_NUM_BYTES(data_type); + int max_num_entities = conf[i].max_num_entities; - sprintf(level->name, "Level %d", level->file_info.nr); + if (num_entities > max_num_entities) + { + Warn("truncating number of entities for element %d from %d to %d", + element, num_entities, max_num_entities); - level->score[SC_EMERALD] = cav->emerald_score; - level->score[SC_DIAMOND] = cav->diamond_score; - level->score[SC_ROBOT] = cav->alien_score; - level->score[SC_SPACESHIP] = cav->tank_score; - level->score[SC_BUG] = cav->bug_score; - level->score[SC_YAMYAM] = cav->eater_score; - level->score[SC_NUT] = cav->nut_score; - level->score[SC_DYNAMITE] = cav->dynamite_score; - level->score[SC_KEY] = cav->key_score; - level->score[SC_TIME_BONUS] = cav->exit_score; + num_entities = max_num_entities; + } - level->num_yamyam_contents = cav->num_eater_arrays; + if (num_entities == 0 && (data_type == TYPE_ELEMENT_LIST || + data_type == TYPE_CONTENT_LIST)) + { + // for element and content lists, zero entities are not allowed + Warn("found empty list of entities for element %d", element); - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - level->yamyam_content[i].e[x][y] = - map_element_EM_to_RND_cave(cav->eater_array[i][y * 3 + x]); + // do not set "num_entities" here to prevent reading behind buffer - level->amoeba_speed = cav->amoeba_time; - level->time_magic_wall = cav->wonderwall_time; - level->time_wheel = cav->wheel_time; + *(int *)(conf[i].num_entities) = 1; // at least one is required + } + else + { + *(int *)(conf[i].num_entities) = num_entities; + } - level->android_move_time = cav->android_move_time; - level->android_clone_time = cav->android_clone_time; - level->ball_random = cav->ball_random; - level->ball_active_initial = cav->ball_active; - level->ball_time = cav->ball_time; - level->num_ball_contents = cav->num_ball_arrays; + element_found = TRUE; - level->lenses_score = cav->lenses_score; - level->magnify_score = cav->magnify_score; - level->slurp_score = cav->slurp_score; + if (data_type == TYPE_STRING) + { + char *string = (char *)(conf[i].value); + int j; - level->lenses_time = cav->lenses_time; - level->magnify_time = cav->magnify_time; + for (j = 0; j < max_num_entities; j++) + string[j] = (j < num_entities ? buffer[j] : '\0'); + } + else if (data_type == TYPE_ELEMENT_LIST) + { + int *element_array = (int *)(conf[i].value); + int j; - level->wind_direction_initial = - map_direction_EM_to_RND(cav->wind_direction); + for (j = 0; j < num_entities; j++) + element_array[j] = + getMappedElement(CONF_ELEMENTS_ELEMENT(buffer, j)); + } + else if (data_type == TYPE_CONTENT_LIST) + { + struct Content *content= (struct Content *)(conf[i].value); + int c, x, y; - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (j = 0; j < 8; j++) - level->ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]] = - map_element_EM_to_RND_cave(cav->ball_array[i][j]); + for (c = 0; c < num_entities; c++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content[c].e[x][y] = + getMappedElement(CONF_CONTENTS_ELEMENT(buffer, c, x, y)); + } + else + element_found = FALSE; - map_android_clone_elements_EM_to_RND(level); + break; + } + } - // convert the playfield (some elements need special treatment) - for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++) + checked_free(buffer); + + micro_chunk_size += 2 + num_bytes; + } + else // constant size configuration data (1, 2 or 4 bytes) { - int new_element = map_element_EM_to_RND_cave(cav->cave[x][y]); + int value = (byte_mask == CONF_MASK_1_BYTE ? getFile8Bit (file) : + byte_mask == CONF_MASK_2_BYTE ? getFile16BitBE(file) : + byte_mask == CONF_MASK_4_BYTE ? getFile32BitBE(file) : 0); - if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0) - new_element = EL_AMOEBA_DEAD; + for (i = 0; conf[i].data_type != -1; i++) + { + if (conf[i].element == element && + conf[i].conf_type == conf_type) + { + int data_type = conf[i].data_type; - level->field[x][y] = new_element; + if (data_type == TYPE_ELEMENT) + value = getMappedElement(value); + + if (data_type == TYPE_BOOLEAN) + *(boolean *)(conf[i].value) = (value ? TRUE : FALSE); + else + *(int *) (conf[i].value) = value; + + element_found = TRUE; + + break; + } + } + + micro_chunk_size += CONF_VALUE_NUM_BYTES(byte_mask); } - for (i = 0; i < MAX_PLAYERS; i++) + if (!element_found) { - // in case of all players set to the same field, use the first player - int nr = MAX_PLAYERS - i - 1; - int jx = cav->player_x[nr]; - int jy = cav->player_y[nr]; + char *error_conf_chunk_bytes = + (byte_mask == CONF_MASK_1_BYTE ? "CONF_VALUE_8_BIT" : + byte_mask == CONF_MASK_2_BYTE ? "CONF_VALUE_16_BIT" : + byte_mask == CONF_MASK_4_BYTE ? "CONF_VALUE_32_BIT" :"CONF_VALUE_BYTES"); + int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN; + int error_element = real_element; - if (jx != -1 && jy != -1) - level->field[jx][jy] = EL_PLAYER_1 + nr; + Warn("cannot load micro chunk '%s(%d)' value for element %d ['%s']", + error_conf_chunk_bytes, error_conf_chunk_token, + error_element, EL_NAME(error_element)); + } + + return micro_chunk_size; +} + +static int LoadLevel_INFO(File *file, int chunk_size, struct LevelInfo *level) +{ + int real_chunk_size = 0; + + li = *level; // copy level data into temporary buffer + + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_INFO, -1, -1); + + if (real_chunk_size >= chunk_size) + break; } - // time score is counted for each 10 seconds left in Emerald Mine levels - level->time_score_base = 10; -} - + *level = li; // copy temporary buffer back to level data -// ---------------------------------------------------------------------------- -// functions for loading SP level -// ---------------------------------------------------------------------------- + return real_chunk_size; +} -static void CopyNativeLevel_RND_to_SP(struct LevelInfo *level) +static int LoadLevel_CONF(File *file, int chunk_size, struct LevelInfo *level) { - struct LevelInfo_SP *level_sp = level->native_sp_level; - LevelInfoType *header = &level_sp->header; - int i, x, y; + int real_chunk_size = 0; - level_sp->width = level->fieldx; - level_sp->height = level->fieldy; + li = *level; // copy level data into temporary buffer - for (x = 0; x < level->fieldx; x++) - for (y = 0; y < level->fieldy; y++) - level_sp->playfield[x][y] = map_element_RND_to_SP(level->field[x][y]); + while (!checkEndOfFile(file)) + { + int element = getMappedElement(getFile16BitBE(file)); - header->InitialGravity = (level->initial_player_gravity[0] ? 1 : 0); + real_chunk_size += 2; + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CONF, + element, element); + if (real_chunk_size >= chunk_size) + break; + } - for (i = 0; i < SP_LEVEL_NAME_LEN; i++) - header->LevelTitle[i] = level->name[i]; - // !!! NO STRING TERMINATION IN SUPAPLEX VB CODE YET -- FIX THIS !!! + *level = li; // copy temporary buffer back to level data - header->InfotronsNeeded = level->gems_needed; + return real_chunk_size; +} - header->SpecialPortCount = 0; +static int LoadLevel_ELEM(File *file, int chunk_size, struct LevelInfo *level) +{ + int real_chunk_size = 0; - for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++) - { - boolean gravity_port_found = FALSE; - boolean gravity_port_valid = FALSE; - int gravity_port_flag; - int gravity_port_base_element; - int element = level->field[x][y]; + li = *level; // copy level data into temporary buffer - if (element >= EL_SP_GRAVITY_ON_PORT_RIGHT && - element <= EL_SP_GRAVITY_ON_PORT_UP) - { - gravity_port_found = TRUE; - gravity_port_valid = TRUE; - gravity_port_flag = 1; - gravity_port_base_element = EL_SP_GRAVITY_ON_PORT_RIGHT; - } - else if (element >= EL_SP_GRAVITY_OFF_PORT_RIGHT && - element <= EL_SP_GRAVITY_OFF_PORT_UP) - { - gravity_port_found = TRUE; - gravity_port_valid = TRUE; - gravity_port_flag = 0; - gravity_port_base_element = EL_SP_GRAVITY_OFF_PORT_RIGHT; - } - else if (element >= EL_SP_GRAVITY_PORT_RIGHT && - element <= EL_SP_GRAVITY_PORT_UP) - { - // change R'n'D style gravity inverting special port to normal port - // (there are no gravity inverting ports in native Supaplex engine) + while (!checkEndOfFile(file)) + { + int element = getMappedElement(getFile16BitBE(file)); - gravity_port_found = TRUE; - gravity_port_valid = FALSE; - gravity_port_base_element = EL_SP_GRAVITY_PORT_RIGHT; - } + real_chunk_size += 2; + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_ELEM, + element, element); + if (real_chunk_size >= chunk_size) + break; + } - if (gravity_port_found) - { - if (gravity_port_valid && - header->SpecialPortCount < SP_MAX_SPECIAL_PORTS) - { - SpecialPortType *port = &header->SpecialPort[header->SpecialPortCount]; + *level = li; // copy temporary buffer back to level data - port->PortLocation = (y * level->fieldx + x) * 2; - port->Gravity = gravity_port_flag; + return real_chunk_size; +} - element += EL_SP_GRAVITY_PORT_RIGHT - gravity_port_base_element; +static int LoadLevel_NOTE(File *file, int chunk_size, struct LevelInfo *level) +{ + int element = getMappedElement(getFile16BitBE(file)); + int envelope_nr = element - EL_ENVELOPE_1; + int real_chunk_size = 2; - header->SpecialPortCount++; - } - else - { - // change special gravity port to normal port + xx_envelope = level->envelope[envelope_nr]; // copy into temporary buffer - element += EL_SP_PORT_RIGHT - gravity_port_base_element; - } + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_NOTE, + -1, element); - level_sp->playfield[x][y] = element - EL_SP_START; - } + if (real_chunk_size >= chunk_size) + break; } + + level->envelope[envelope_nr] = xx_envelope; // copy from temporary buffer + + return real_chunk_size; } -static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) +static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) { - struct LevelInfo_SP *level_sp = level->native_sp_level; - LevelInfoType *header = &level_sp->header; - boolean num_invalid_elements = 0; - int i, j, x, y; - - level->fieldx = level_sp->width; - level->fieldy = level_sp->height; + int element = getMappedElement(getFile16BitBE(file)); + int real_chunk_size = 2; + struct ElementInfo *ei = &element_info[element]; + int i; - for (x = 0; x < level->fieldx; x++) - { - for (y = 0; y < level->fieldy; y++) - { - int element_old = level_sp->playfield[x][y]; - int element_new = getMappedElement(map_element_SP_to_RND(element_old)); + xx_ei = *ei; // copy element data into temporary buffer - if (element_new == EL_UNKNOWN) - { - num_invalid_elements++; + xx_ei.num_change_pages = -1; - Debug("level:native:SP", "invalid element %d at position %d, %d", - element_old, x, y); - } + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CUSX_base, + -1, element); + if (xx_ei.num_change_pages != -1) + break; - level->field[x][y] = element_new; - } + if (real_chunk_size >= chunk_size) + break; } - if (num_invalid_elements > 0) - Warn("found %d invalid elements%s", num_invalid_elements, - (!options.debug ? " (use '--debug' for more details)" : "")); + *ei = xx_ei; - for (i = 0; i < MAX_PLAYERS; i++) - level->initial_player_gravity[i] = - (header->InitialGravity == 1 ? TRUE : FALSE); + if (ei->num_change_pages == -1) + { + Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", + EL_NAME(element)); - // skip leading spaces - for (i = 0; i < SP_LEVEL_NAME_LEN; i++) - if (header->LevelTitle[i] != ' ') - break; + ei->num_change_pages = 1; - // copy level title - for (j = 0; i < SP_LEVEL_NAME_LEN; i++, j++) - level->name[j] = header->LevelTitle[i]; - level->name[j] = '\0'; + setElementChangePages(ei, 1); + setElementChangeInfoToDefaults(ei->change); - // cut trailing spaces - for (; j > 0; j--) - if (level->name[j - 1] == ' ' && level->name[j] == '\0') - level->name[j - 1] = '\0'; + return real_chunk_size; + } - level->gems_needed = header->InfotronsNeeded; + // initialize number of change pages stored for this custom element + setElementChangePages(ei, ei->num_change_pages); + for (i = 0; i < ei->num_change_pages; i++) + setElementChangeInfoToDefaults(&ei->change_page[i]); - for (i = 0; i < header->SpecialPortCount; i++) + // start with reading properties for the first change page + xx_current_change_page = 0; + + while (!checkEndOfFile(file)) { - SpecialPortType *port = &header->SpecialPort[i]; - int port_location = port->PortLocation; - int gravity = port->Gravity; - int port_x, port_y, port_element; + // level file might contain invalid change page number + if (xx_current_change_page >= ei->num_change_pages) + break; - port_x = (port_location / 2) % level->fieldx; - port_y = (port_location / 2) / level->fieldx; + struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page]; - if (port_x < 0 || port_x >= level->fieldx || - port_y < 0 || port_y >= level->fieldy) - { - Warn("special port position (%d, %d) out of bounds", port_x, port_y); + xx_change = *change; // copy change data into temporary buffer - continue; - } + resetEventBits(); // reset bits; change page might have changed - port_element = level->field[port_x][port_y]; + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_CUSX_change, + -1, element); - if (port_element < EL_SP_GRAVITY_PORT_RIGHT || - port_element > EL_SP_GRAVITY_PORT_UP) - { - Warn("no special port at position (%d, %d)", port_x, port_y); + *change = xx_change; - continue; - } + setEventFlagsFromEventBits(change); - // 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; + if (real_chunk_size >= chunk_size) + break; } - // change special gravity ports without database entries to normal ports - for (x = 0; x < level->fieldx; x++) - for (y = 0; y < level->fieldy; y++) - 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; + level->file_has_custom_elements = TRUE; - level->time = 0; // no time limit - level->amoeba_speed = 0; - level->time_magic_wall = 0; - level->time_wheel = 0; - level->amoeba_content = EL_EMPTY; + return real_chunk_size; +} - // original Supaplex does not use score values -- rate by playing time - for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) - level->score[i] = 0; +static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) +{ + int element = getMappedElement(getFile16BitBE(file)); + int real_chunk_size = 2; + struct ElementInfo *ei = &element_info[element]; + struct ElementGroupInfo *group = ei->group; + + if (group == NULL) + return -1; - level->rate_time_over_score = TRUE; + xx_ei = *ei; // copy element data into temporary buffer + xx_group = *group; // copy group data into temporary buffer - // there are no yamyams in supaplex levels - for (i = 0; i < level->num_yamyam_contents; i++) - for (x = 0; x < 3; x++) - for (y = 0; y < 3; y++) - level->yamyam_content[i].e[x][y] = EL_EMPTY; -} + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_GRPX, + -1, element); -static void CopyNativeTape_RND_to_SP(struct LevelInfo *level) -{ - struct LevelInfo_SP *level_sp = level->native_sp_level; - struct DemoInfo_SP *demo = &level_sp->demo; - int i, j; + if (real_chunk_size >= chunk_size) + break; + } - // always start with reliable default values - demo->is_available = FALSE; - demo->length = 0; + *ei = xx_ei; + *group = xx_group; - if (TAPE_IS_EMPTY(tape)) - return; + level->file_has_custom_elements = TRUE; - demo->level_nr = tape.level_nr; // (currently not used) + return real_chunk_size; +} - level_sp->header.DemoRandomSeed = tape.random_seed; +static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level) +{ + int element = getMappedElement(getFile16BitBE(file)); + int real_chunk_size = 2; + struct ElementInfo *ei = &element_info[element]; - demo->length = 0; + xx_ei = *ei; // copy element data into temporary buffer - for (i = 0; i < tape.length; i++) + while (!checkEndOfFile(file)) { - int demo_action = map_key_RND_to_SP(tape.pos[i].action[0]); - int demo_repeat = tape.pos[i].delay; - int demo_entries = (demo_repeat + 15) / 16; - - if (demo->length + demo_entries >= SP_MAX_TAPE_LEN) - { - Warn("tape truncated: size exceeds maximum SP demo size %d", - SP_MAX_TAPE_LEN); + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX, + -1, element); + if (real_chunk_size >= chunk_size) break; - } + } - for (j = 0; j < demo_repeat / 16; j++) - demo->data[demo->length++] = 0xf0 | demo_action; + *ei = xx_ei; - if (demo_repeat % 16) - demo->data[demo->length++] = ((demo_repeat % 16 - 1) << 4) | demo_action; - } + level->file_has_custom_elements = TRUE; - demo->is_available = TRUE; + return real_chunk_size; } -static void setTapeInfoToDefaults(void); - -static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) +static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - struct LevelInfo_SP *level_sp = level->native_sp_level; - struct DemoInfo_SP *demo = &level_sp->demo; - char *filename = level->file_info.filename; - int i; + char *filename = level_file_info->filename; + char cookie[MAX_LINE_LEN]; + char chunk_name[CHUNK_ID_LEN + 1]; + int chunk_size; + File *file; - // always start with reliable default values - setTapeInfoToDefaults(); + if (!(file = openFile(filename, MODE_READ))) + { + level->no_valid_file = TRUE; + level->no_level_file = TRUE; - if (!demo->is_available) - return; + if (level_info_only) + return; - tape.level_nr = demo->level_nr; // (currently not used) - tape.random_seed = level_sp->header.DemoRandomSeed; + Warn("cannot read level '%s' -- using empty level", filename); - TapeSetDateFromEpochSeconds(getFileTimestampEpochSeconds(filename)); + if (!setup.editor.use_template_for_new_levels) + return; - tape.counter = 0; - tape.pos[tape.counter].delay = 0; + // if level file not found, try to initialize level data from template + filename = getGlobalLevelTemplateFilename(); - for (i = 0; i < demo->length; i++) - { - int demo_action = demo->data[i] & 0x0f; - int demo_repeat = (demo->data[i] & 0xf0) >> 4; - int tape_action = map_key_SP_to_RND(demo_action); - int tape_repeat = demo_repeat + 1; - byte action[MAX_TAPE_ACTIONS] = { tape_action }; - boolean success = 0; - int j; + if (!(file = openFile(filename, MODE_READ))) + return; - for (j = 0; j < tape_repeat; j++) - success = TapeAddAction(action); + // default: for empty levels, use level template for custom elements + level->use_custom_template = TRUE; - if (!success) + level->no_valid_file = FALSE; + } + + getFileChunkBE(file, chunk_name, NULL); + if (strEqual(chunk_name, "RND1")) + { + getFile32BitBE(file); // not used + + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "CAVE")) { - Warn("SP demo truncated: size exceeds maximum tape size %d", - MAX_TAPE_LEN); + level->no_valid_file = TRUE; - break; + Warn("unknown format of level file '%s'", filename); + + closeFile(file); + + return; } } + else // check for pre-2.0 file format with cookie string + { + strcpy(cookie, chunk_name); + if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) + cookie[4] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; - TapeHaltRecording(); -} + if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL)) + { + level->no_valid_file = TRUE; + Warn("unknown format of level file '%s'", filename); -// ---------------------------------------------------------------------------- -// functions for loading MM level -// ---------------------------------------------------------------------------- + closeFile(file); -static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) -{ - struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + return; + } - level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH); - level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT); + if ((level->file_version = getFileVersionFromCookieString(cookie)) == -1) + { + level->no_valid_file = TRUE; - level_mm->time = level->time; - level_mm->kettles_needed = level->gems_needed; - level_mm->auto_count_kettles = level->auto_count_gems; + Warn("unsupported version of level file '%s'", filename); - level_mm->laser_red = level->mm_laser_red; - level_mm->laser_green = level->mm_laser_green; - level_mm->laser_blue = level->mm_laser_blue; + closeFile(file); - strcpy(level_mm->name, level->name); - strcpy(level_mm->author, level->author); + return; + } - level_mm->score[SC_EMERALD] = level->score[SC_EMERALD]; - level_mm->score[SC_PACMAN] = level->score[SC_PACMAN]; - level_mm->score[SC_KEY] = level->score[SC_KEY]; - level_mm->score[SC_TIME_BONUS] = level->score[SC_TIME_BONUS]; - level_mm->score[SC_ELEM_BONUS] = level->score[SC_ELEM_BONUS]; + // pre-2.0 level files have no game version, so use file version here + level->game_version = level->file_version; + } - level_mm->amoeba_speed = level->amoeba_speed; - level_mm->time_fuse = level->mm_time_fuse; - level_mm->time_bomb = level->mm_time_bomb; - level_mm->time_ball = level->mm_time_ball; - level_mm->time_block = level->mm_time_block; + if (level->file_version < FILE_VERSION_1_2) + { + // level files from versions before 1.2.0 without chunk structure + LoadLevel_HEAD(file, LEVEL_CHUNK_HEAD_SIZE, level); + LoadLevel_BODY(file, level->fieldx * level->fieldy, level); + } + else + { + static struct + { + char *name; + int size; + int (*loader)(File *, int, struct LevelInfo *); + } + chunk_info[] = + { + { "VERS", LEVEL_CHUNK_VERS_SIZE, LoadLevel_VERS }, + { "DATE", LEVEL_CHUNK_DATE_SIZE, LoadLevel_DATE }, + { "HEAD", LEVEL_CHUNK_HEAD_SIZE, LoadLevel_HEAD }, + { "NAME", LEVEL_CHUNK_NAME_SIZE, LoadLevel_NAME }, + { "AUTH", LEVEL_CHUNK_AUTH_SIZE, LoadLevel_AUTH }, + { "INFO", -1, LoadLevel_INFO }, + { "BODY", -1, LoadLevel_BODY }, + { "CONT", -1, LoadLevel_CONT }, + { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 }, + { "CNT3", -1, LoadLevel_CNT3 }, + { "CUS1", -1, LoadLevel_CUS1 }, + { "CUS2", -1, LoadLevel_CUS2 }, + { "CUS3", -1, LoadLevel_CUS3 }, + { "CUS4", -1, LoadLevel_CUS4 }, + { "GRP1", -1, LoadLevel_GRP1 }, + { "CONF", -1, LoadLevel_CONF }, + { "ELEM", -1, LoadLevel_ELEM }, + { "NOTE", -1, LoadLevel_NOTE }, + { "CUSX", -1, LoadLevel_CUSX }, + { "GRPX", -1, LoadLevel_GRPX }, + { "EMPX", -1, LoadLevel_EMPX }, - for (x = 0; x < level->fieldx; x++) - for (y = 0; y < level->fieldy; y++) - Ur[x][y] = - level_mm->field[x][y] = map_element_RND_to_MM(level->field[x][y]); -} + { NULL, 0, NULL } + }; -static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) -{ - struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + while (getFileChunkBE(file, chunk_name, &chunk_size)) + { + int i = 0; - level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX); - level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY); + while (chunk_info[i].name != NULL && + !strEqual(chunk_name, chunk_info[i].name)) + i++; - level->time = level_mm->time; - level->gems_needed = level_mm->kettles_needed; - level->auto_count_gems = level_mm->auto_count_kettles; + if (chunk_info[i].name == NULL) + { + Warn("unknown chunk '%s' in level file '%s'", + chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); - level->mm_laser_red = level_mm->laser_red; - level->mm_laser_green = level_mm->laser_green; - level->mm_laser_blue = level_mm->laser_blue; + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this level chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, level); - strcpy(level->name, level_mm->name); + if (chunk_size_expected < 0) + { + Warn("error reading chunk '%s' in level file '%s'", + chunk_name, filename); - // only overwrite author from 'levelinfo.conf' if author defined in level - if (!strEqual(level_mm->author, ANONYMOUS_NAME)) - strcpy(level->author, level_mm->author); + break; + } - level->score[SC_EMERALD] = level_mm->score[SC_EMERALD]; - level->score[SC_PACMAN] = level_mm->score[SC_PACMAN]; - level->score[SC_KEY] = level_mm->score[SC_KEY]; - level->score[SC_TIME_BONUS] = level_mm->score[SC_TIME_BONUS]; - level->score[SC_ELEM_BONUS] = level_mm->score[SC_ELEM_BONUS]; + // the size of some chunks cannot be checked before reading other + // chunks first (like "HEAD" and "BODY") that contain some header + // information, so check them here + if (chunk_size_expected != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); - level->amoeba_speed = level_mm->amoeba_speed; - level->mm_time_fuse = level_mm->time_fuse; - level->mm_time_bomb = level_mm->time_bomb; - level->mm_time_ball = level_mm->time_ball; - level->mm_time_block = level_mm->time_block; + break; + } + } + } + } - for (x = 0; x < level->fieldx; x++) - for (y = 0; y < level->fieldy; y++) - level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]); + closeFile(file); } // ---------------------------------------------------------------------------- -// functions for loading DC level +// functions for loading BD level // ---------------------------------------------------------------------------- -#define DC_LEVEL_HEADER_SIZE 344 +#define LEVEL_TO_CAVE(e) (map_element_RND_to_BD_cave(e)) +#define CAVE_TO_LEVEL(e) (map_element_BD_to_RND_cave(e)) -static unsigned short getDecodedWord_DC(unsigned short data_encoded, - boolean init) +static void CopyNativeLevel_RND_to_BD(struct LevelInfo *level) { - static int last_data_encoded; - static int offset1; - static int offset2; - int diff; - int diff_hi, diff_lo; - int data_hi, data_lo; - unsigned short data_decoded; + struct LevelInfo_BD *level_bd = level->native_bd_level; + GdCave *cave = NULL; // will be changed below + int cave_w = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH); + int cave_h = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT); + int x, y; - if (init) - { - last_data_encoded = 0; - offset1 = -1; - offset2 = 0; + setLevelInfoToDefaults_BD_Ext(cave_w, cave_h); + + // cave and map newly allocated when set to defaults above + cave = level_bd->cave; + + // level type + cave->intermission = level->bd_intermission; + + // level settings + cave->level_time[0] = level->time; + cave->level_diamonds[0] = level->gems_needed; + + // game timing + cave->scheduling = level->bd_scheduling_type; + cave->pal_timing = level->bd_pal_timing; + cave->level_speed[0] = level->bd_cycle_delay_ms; + cave->level_ckdelay[0] = level->bd_cycle_delay_c64; + cave->level_hatching_delay_frame[0] = level->bd_hatching_delay_cycles; + cave->level_hatching_delay_time[0] = level->bd_hatching_delay_seconds; + + // scores + cave->level_timevalue[0] = level->score[SC_TIME_BONUS]; + cave->diamond_value = level->score[SC_EMERALD]; + cave->extra_diamond_value = level->score[SC_DIAMOND_EXTRA]; + + // compatibility settings + cave->lineshift = level->bd_line_shifting_borders; + cave->border_scan_first_and_last = level->bd_scan_first_and_last_row; + cave->short_explosions = level->bd_short_explosions; + + // player properties + cave->diagonal_movements = level->bd_diagonal_movements; + cave->active_is_first_found = level->bd_topmost_player_active; + cave->pushing_stone_prob = level->bd_pushing_prob * 10000; + cave->pushing_stone_prob_sweet = level->bd_pushing_prob_with_sweet * 10000; + cave->mega_stones_pushable_with_sweet = level->bd_push_mega_rock_with_sweet; + cave->snap_element = LEVEL_TO_CAVE(level->bd_snap_element); + + // element properties + cave->level_bonus_time[0] = level->bd_clock_extra_time; + cave->voodoo_collects_diamonds = level->bd_voodoo_collects_diamonds; + cave->voodoo_any_hurt_kills_player = level->bd_voodoo_hurt_kills_player; + cave->voodoo_dies_by_stone = level->bd_voodoo_dies_by_rock; + cave->voodoo_disappear_in_explosion = level->bd_voodoo_vanish_by_explosion; + cave->level_penalty_time[0] = level->bd_voodoo_penalty_time; + cave->level_magic_wall_time[0] = level->bd_magic_wall_time; + cave->magic_timer_zero_is_infinite = level->bd_magic_wall_zero_infinite; + cave->magic_timer_wait_for_hatching = level->bd_magic_wall_wait_hatching; + cave->magic_wall_stops_amoeba = level->bd_magic_wall_stops_amoeba; + cave->magic_wall_breakscan = level->bd_magic_wall_break_scan; + + cave->magic_diamond_to = LEVEL_TO_CAVE(level->bd_magic_wall_diamond_to); + cave->magic_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_rock_to); + cave->magic_mega_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_mega_rock_to); + cave->magic_nut_to = LEVEL_TO_CAVE(level->bd_magic_wall_nut_to); + cave->magic_nitro_pack_to = LEVEL_TO_CAVE(level->bd_magic_wall_nitro_pack_to); + cave->magic_flying_diamond_to = LEVEL_TO_CAVE(level->bd_magic_wall_flying_diamond_to); + cave->magic_flying_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_flying_rock_to); + + cave->amoeba_timer_wait_for_hatching = level->bd_amoeba_wait_for_hatching; + cave->amoeba_timer_started_immediately= level->bd_amoeba_start_immediately; + cave->amoeba_2_explodes_by_amoeba = level->bd_amoeba_2_explode_by_amoeba; + cave->level_amoeba_threshold[0] = level->bd_amoeba_1_threshold_too_big; + cave->level_amoeba_time[0] = level->bd_amoeba_1_slow_growth_time; + cave->amoeba_growth_prob = level->bd_amoeba_1_slow_growth_rate * 10000; + cave->amoeba_fast_growth_prob = level->bd_amoeba_1_fast_growth_rate * 10000; + cave->level_amoeba_2_threshold[0] = level->bd_amoeba_2_threshold_too_big; + cave->level_amoeba_2_time[0] = level->bd_amoeba_2_slow_growth_time; + cave->amoeba_2_growth_prob = level->bd_amoeba_2_slow_growth_rate * 10000; + cave->amoeba_2_fast_growth_prob = level->bd_amoeba_2_fast_growth_rate * 10000; + + cave->amoeba_too_big_effect = LEVEL_TO_CAVE(level->bd_amoeba_1_content_too_big); + cave->amoeba_enclosed_effect = LEVEL_TO_CAVE(level->bd_amoeba_1_content_enclosed); + cave->amoeba_2_too_big_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_too_big); + cave->amoeba_2_enclosed_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_enclosed); + cave->amoeba_2_explosion_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_exploding); + cave->amoeba_2_looks_like = LEVEL_TO_CAVE(level->bd_amoeba_2_content_looks_like); + + cave->slime_predictable = level->bd_slime_is_predictable; + cave->slime_correct_random = level->bd_slime_correct_random; + cave->level_slime_permeability[0] = level->bd_slime_permeability_rate * 10000; + cave->level_slime_permeability_c64[0] = level->bd_slime_permeability_bits_c64; + cave->level_slime_seed_c64[0] = level->bd_slime_random_seed_c64; + cave->level_rand[0] = level->bd_cave_random_seed_c64; + cave->slime_eats_1 = LEVEL_TO_CAVE(level->bd_slime_eats_element_1); + cave->slime_converts_1 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_1); + cave->slime_eats_2 = LEVEL_TO_CAVE(level->bd_slime_eats_element_2); + cave->slime_converts_2 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_2); + cave->slime_eats_3 = LEVEL_TO_CAVE(level->bd_slime_eats_element_3); + cave->slime_converts_3 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_3); + + cave->acid_eats_this = LEVEL_TO_CAVE(level->bd_acid_eats_element); + cave->acid_spread_ratio = level->bd_acid_spread_rate * 10000; + cave->acid_turns_to = LEVEL_TO_CAVE(level->bd_acid_turns_to_element); + + cave->biter_delay_frame = level->bd_biter_move_delay; + cave->biter_eat = LEVEL_TO_CAVE(level->bd_biter_eats_element); + + cave->bladder_converts_by = LEVEL_TO_CAVE(level->bd_bladder_converts_by_element); + + cave->expanding_wall_changed = level->bd_change_expanding_wall; + + cave->replicators_active = level->bd_replicators_active; + cave->replicator_delay_frame = level->bd_replicator_create_delay; + + cave->conveyor_belts_active = level->bd_conveyor_belts_active; + cave->conveyor_belts_direction_changed= level->bd_conveyor_belts_changed; + + cave->water_does_not_flow_down = level->bd_water_cannot_flow_down; + + cave->nut_turns_to_when_crushed = LEVEL_TO_CAVE(level->bd_nut_content); + + cave->pneumatic_hammer_frame = level->bd_hammer_walls_break_delay; + cave->hammered_walls_reappear = level->bd_hammer_walls_reappear; + cave->hammered_wall_reappear_frame = level->bd_hammer_walls_reappear_delay; + + cave->infinite_rockets = level->bd_infinite_rockets; + + cave->skeletons_needed_for_pot = level->bd_num_skeletons_needed_for_pot; + cave->skeletons_worth_diamonds = level->bd_skeleton_worth_num_diamonds; + + cave->expanding_wall_looks_like = LEVEL_TO_CAVE(level->bd_expanding_wall_looks_like); + cave->dirt_looks_like = LEVEL_TO_CAVE(level->bd_sand_looks_like); + + cave->creatures_backwards = level->bd_creatures_start_backwards; + cave->creatures_direction_auto_change_on_start = level->bd_creatures_turn_on_hatching; + cave->creatures_direction_auto_change_time = level->bd_creatures_auto_turn_delay; + + cave->gravity = level->bd_gravity_direction; + cave->gravity_switch_active = level->bd_gravity_switch_active; + cave->gravity_change_time = level->bd_gravity_switch_delay; + cave->gravity_affects_all = level->bd_gravity_affects_all; + + cave->stone_falling_effect = LEVEL_TO_CAVE(level->bd_rock_turns_to_on_falling); + cave->stone_bouncing_effect = LEVEL_TO_CAVE(level->bd_rock_turns_to_on_impact); + cave->diamond_falling_effect = LEVEL_TO_CAVE(level->bd_diamond_turns_to_on_falling); + cave->diamond_bouncing_effect = LEVEL_TO_CAVE(level->bd_diamond_turns_to_on_impact); + + cave->firefly_explode_to = LEVEL_TO_CAVE(level->bd_firefly_1_explodes_to); + cave->alt_firefly_explode_to = LEVEL_TO_CAVE(level->bd_firefly_2_explodes_to); + cave->butterfly_explode_to = LEVEL_TO_CAVE(level->bd_butterfly_1_explodes_to); + cave->alt_butterfly_explode_to = LEVEL_TO_CAVE(level->bd_butterfly_2_explodes_to); + cave->stonefly_explode_to = LEVEL_TO_CAVE(level->bd_stonefly_explodes_to); + cave->dragonfly_explode_to = LEVEL_TO_CAVE(level->bd_dragonfly_explodes_to); + + cave->diamond_birth_effect = LEVEL_TO_CAVE(level->bd_diamond_birth_turns_to); + cave->bomb_explosion_effect = LEVEL_TO_CAVE(level->bd_bomb_explosion_turns_to); + cave->nitro_explosion_effect = LEVEL_TO_CAVE(level->bd_nitro_explosion_turns_to); + cave->explosion_effect = LEVEL_TO_CAVE(level->bd_explosion_turns_to); + + cave->colorb = level->bd_color_b; + cave->color0 = level->bd_color_0; + cave->color1 = level->bd_color_1; + cave->color2 = level->bd_color_2; + cave->color3 = level->bd_color_3; + cave->color4 = level->bd_color_4; + cave->color5 = level->bd_color_5; + + // level name + strncpy(cave->name, level->name, sizeof(GdString)); + cave->name[sizeof(GdString) - 1] = '\0'; + + // playfield elements + for (x = 0; x < cave->w; x++) + for (y = 0; y < cave->h; y++) + cave->map[y][x] = LEVEL_TO_CAVE(level->field[x][y]); +} + +static void CopyNativeLevel_BD_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_BD *level_bd = level->native_bd_level; + GdCave *cave = level_bd->cave; + int bd_level_nr = level_bd->level_nr; + int x, y; - return 0; - } + level->fieldx = MIN(cave->w, MAX_LEV_FIELDX); + level->fieldy = MIN(cave->h, MAX_LEV_FIELDY); + + // level type + level->bd_intermission = cave->intermission; + + // level settings + level->time = cave->level_time[bd_level_nr]; + level->gems_needed = cave->level_diamonds[bd_level_nr]; + + // game timing + level->bd_scheduling_type = cave->scheduling; + level->bd_pal_timing = cave->pal_timing; + level->bd_cycle_delay_ms = cave->level_speed[bd_level_nr]; + level->bd_cycle_delay_c64 = cave->level_ckdelay[bd_level_nr]; + level->bd_hatching_delay_cycles = cave->level_hatching_delay_frame[bd_level_nr]; + level->bd_hatching_delay_seconds = cave->level_hatching_delay_time[bd_level_nr]; + + // scores + level->score[SC_TIME_BONUS] = cave->level_timevalue[bd_level_nr]; + level->score[SC_EMERALD] = cave->diamond_value; + level->score[SC_DIAMOND_EXTRA] = cave->extra_diamond_value; + + // compatibility settings + level->bd_line_shifting_borders = cave->lineshift; + level->bd_scan_first_and_last_row = cave->border_scan_first_and_last; + level->bd_short_explosions = cave->short_explosions; + + // player properties + level->bd_diagonal_movements = cave->diagonal_movements; + level->bd_topmost_player_active = cave->active_is_first_found; + level->bd_pushing_prob = cave->pushing_stone_prob / 10000; + level->bd_pushing_prob_with_sweet = cave->pushing_stone_prob_sweet / 10000; + level->bd_push_mega_rock_with_sweet = cave->mega_stones_pushable_with_sweet; + level->bd_snap_element = CAVE_TO_LEVEL(cave->snap_element); + + // element properties + level->bd_clock_extra_time = cave->level_bonus_time[bd_level_nr]; + level->bd_voodoo_collects_diamonds = cave->voodoo_collects_diamonds; + level->bd_voodoo_hurt_kills_player = cave->voodoo_any_hurt_kills_player; + level->bd_voodoo_dies_by_rock = cave->voodoo_dies_by_stone; + level->bd_voodoo_vanish_by_explosion = cave->voodoo_disappear_in_explosion; + level->bd_voodoo_penalty_time = cave->level_penalty_time[bd_level_nr]; + level->bd_magic_wall_time = cave->level_magic_wall_time[bd_level_nr]; + level->bd_magic_wall_zero_infinite = cave->magic_timer_zero_is_infinite; + level->bd_magic_wall_wait_hatching = cave->magic_timer_wait_for_hatching; + level->bd_magic_wall_stops_amoeba = cave->magic_wall_stops_amoeba; + level->bd_magic_wall_break_scan = cave->magic_wall_breakscan; + + level->bd_magic_wall_diamond_to = CAVE_TO_LEVEL(cave->magic_diamond_to); + level->bd_magic_wall_rock_to = CAVE_TO_LEVEL(cave->magic_stone_to); + level->bd_magic_wall_mega_rock_to = CAVE_TO_LEVEL(cave->magic_mega_stone_to); + level->bd_magic_wall_nut_to = CAVE_TO_LEVEL(cave->magic_nut_to); + level->bd_magic_wall_nitro_pack_to = CAVE_TO_LEVEL(cave->magic_nitro_pack_to); + level->bd_magic_wall_flying_diamond_to= CAVE_TO_LEVEL(cave->magic_flying_diamond_to); + level->bd_magic_wall_flying_rock_to = CAVE_TO_LEVEL(cave->magic_flying_stone_to); + + level->bd_amoeba_wait_for_hatching = cave->amoeba_timer_wait_for_hatching; + level->bd_amoeba_start_immediately = cave->amoeba_timer_started_immediately; + level->bd_amoeba_2_explode_by_amoeba = cave->amoeba_2_explodes_by_amoeba; + level->bd_amoeba_1_threshold_too_big = cave->level_amoeba_threshold[bd_level_nr]; + level->bd_amoeba_1_slow_growth_time = cave->level_amoeba_time[bd_level_nr]; + level->bd_amoeba_1_slow_growth_rate = cave->amoeba_growth_prob / 10000; + level->bd_amoeba_1_fast_growth_rate = cave->amoeba_fast_growth_prob / 10000; + level->bd_amoeba_2_threshold_too_big = cave->level_amoeba_2_threshold[bd_level_nr]; + level->bd_amoeba_2_slow_growth_time = cave->level_amoeba_2_time[bd_level_nr]; + level->bd_amoeba_2_slow_growth_rate = cave->amoeba_2_growth_prob / 10000; + level->bd_amoeba_2_fast_growth_rate = cave->amoeba_2_fast_growth_prob / 10000; + + level->bd_amoeba_1_content_too_big = CAVE_TO_LEVEL(cave->amoeba_too_big_effect); + level->bd_amoeba_1_content_enclosed = CAVE_TO_LEVEL(cave->amoeba_enclosed_effect); + level->bd_amoeba_2_content_too_big = CAVE_TO_LEVEL(cave->amoeba_2_too_big_effect); + level->bd_amoeba_2_content_enclosed = CAVE_TO_LEVEL(cave->amoeba_2_enclosed_effect); + level->bd_amoeba_2_content_exploding = CAVE_TO_LEVEL(cave->amoeba_2_explosion_effect); + level->bd_amoeba_2_content_looks_like = CAVE_TO_LEVEL(cave->amoeba_2_looks_like); + + level->bd_slime_is_predictable = cave->slime_predictable; + level->bd_slime_correct_random = cave->slime_correct_random; + level->bd_slime_permeability_rate = cave->level_slime_permeability[bd_level_nr] / 10000; + level->bd_slime_permeability_bits_c64 = cave->level_slime_permeability_c64[bd_level_nr]; + level->bd_slime_random_seed_c64 = cave->level_slime_seed_c64[bd_level_nr]; + level->bd_cave_random_seed_c64 = cave->level_rand[bd_level_nr]; + level->bd_slime_eats_element_1 = CAVE_TO_LEVEL(cave->slime_eats_1); + level->bd_slime_converts_to_element_1 = CAVE_TO_LEVEL(cave->slime_converts_1); + level->bd_slime_eats_element_2 = CAVE_TO_LEVEL(cave->slime_eats_2); + level->bd_slime_converts_to_element_2 = CAVE_TO_LEVEL(cave->slime_converts_2); + level->bd_slime_eats_element_3 = CAVE_TO_LEVEL(cave->slime_eats_3); + level->bd_slime_converts_to_element_3 = CAVE_TO_LEVEL(cave->slime_converts_3); + + level->bd_acid_eats_element = CAVE_TO_LEVEL(cave->acid_eats_this); + level->bd_acid_spread_rate = cave->acid_spread_ratio / 10000; + level->bd_acid_turns_to_element = CAVE_TO_LEVEL(cave->acid_turns_to); + + level->bd_biter_move_delay = cave->biter_delay_frame; + level->bd_biter_eats_element = CAVE_TO_LEVEL(cave->biter_eat); + + level->bd_bladder_converts_by_element = CAVE_TO_LEVEL(cave->bladder_converts_by); + + level->bd_change_expanding_wall = cave->expanding_wall_changed; + + level->bd_replicators_active = cave->replicators_active; + level->bd_replicator_create_delay = cave->replicator_delay_frame; + + level->bd_conveyor_belts_active = cave->conveyor_belts_active; + level->bd_conveyor_belts_changed = cave->conveyor_belts_direction_changed; + + level->bd_water_cannot_flow_down = cave->water_does_not_flow_down; + + level->bd_nut_content = CAVE_TO_LEVEL(cave->nut_turns_to_when_crushed); + + level->bd_hammer_walls_break_delay = cave->pneumatic_hammer_frame; + level->bd_hammer_walls_reappear = cave->hammered_walls_reappear; + level->bd_hammer_walls_reappear_delay = cave->hammered_wall_reappear_frame; + + level->bd_infinite_rockets = cave->infinite_rockets; + + level->bd_num_skeletons_needed_for_pot= cave->skeletons_needed_for_pot; + level->bd_skeleton_worth_num_diamonds = cave->skeletons_worth_diamonds; + + level->bd_expanding_wall_looks_like = CAVE_TO_LEVEL(cave->expanding_wall_looks_like); + level->bd_sand_looks_like = CAVE_TO_LEVEL(cave->dirt_looks_like); + + level->bd_creatures_start_backwards = cave->creatures_backwards; + level->bd_creatures_turn_on_hatching = cave->creatures_direction_auto_change_on_start; + level->bd_creatures_auto_turn_delay = cave->creatures_direction_auto_change_time; + + level->bd_gravity_direction = cave->gravity; + level->bd_gravity_switch_active = cave->gravity_switch_active; + level->bd_gravity_switch_delay = cave->gravity_change_time; + level->bd_gravity_affects_all = cave->gravity_affects_all; + + level->bd_rock_turns_to_on_falling = CAVE_TO_LEVEL(cave->stone_falling_effect); + level->bd_rock_turns_to_on_impact = CAVE_TO_LEVEL(cave->stone_bouncing_effect); + level->bd_diamond_turns_to_on_falling = CAVE_TO_LEVEL(cave->diamond_falling_effect); + level->bd_diamond_turns_to_on_impact = CAVE_TO_LEVEL(cave->diamond_bouncing_effect); + + level->bd_firefly_1_explodes_to = CAVE_TO_LEVEL(cave->firefly_explode_to); + level->bd_firefly_2_explodes_to = CAVE_TO_LEVEL(cave->alt_firefly_explode_to); + level->bd_butterfly_1_explodes_to = CAVE_TO_LEVEL(cave->butterfly_explode_to); + level->bd_butterfly_2_explodes_to = CAVE_TO_LEVEL(cave->alt_butterfly_explode_to); + level->bd_stonefly_explodes_to = CAVE_TO_LEVEL(cave->stonefly_explode_to); + level->bd_dragonfly_explodes_to = CAVE_TO_LEVEL(cave->dragonfly_explode_to); + + level->bd_diamond_birth_turns_to = CAVE_TO_LEVEL(cave->diamond_birth_effect); + level->bd_bomb_explosion_turns_to = CAVE_TO_LEVEL(cave->bomb_explosion_effect); + level->bd_nitro_explosion_turns_to = CAVE_TO_LEVEL(cave->nitro_explosion_effect); + level->bd_explosion_turns_to = CAVE_TO_LEVEL(cave->explosion_effect); + + level->bd_color_b = cave->colorb; + level->bd_color_0 = cave->color0; + level->bd_color_1 = cave->color1; + level->bd_color_2 = cave->color2; + level->bd_color_3 = cave->color3; + level->bd_color_4 = cave->color4; + level->bd_color_5 = cave->color5; + + // set default color type and colors for BD style level colors + SetDefaultLevelColorType_BD(); + SetDefaultLevelColors_BD(); + + // level name + char *cave_name = getStringPrint("%s / %d", cave->name, bd_level_nr + 1); + + strncpy(level->name, cave_name, MAX_LEVEL_NAME_LEN); + level->name[MAX_LEVEL_NAME_LEN] = '\0'; + + // playfield elements + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + level->field[x][y] = CAVE_TO_LEVEL(cave->map[y][x]); - diff = data_encoded - last_data_encoded; - diff_hi = diff & ~0xff; - diff_lo = diff & 0xff; + checked_free(cave_name); +} - offset2 += diff_lo; +static void setTapeInfoToDefaults(void); - data_hi = diff_hi - (offset1 << 8) + (offset2 & 0xff00); - data_lo = (diff_lo + (data_hi >> 16)) & 0x00ff; - data_hi = data_hi & 0xff00; +static void CopyNativeTape_BD_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_BD *level_bd = level->native_bd_level; + GdCave *cave = level_bd->cave; + GdReplay *replay = level_bd->replay; + int i; - data_decoded = data_hi | data_lo; + if (replay == NULL) + return; - last_data_encoded = data_encoded; + // always start with reliable default values + setTapeInfoToDefaults(); - offset1 = (offset1 + 1) % 31; - offset2 = offset2 & 0xff; + tape.level_nr = level_nr; // (currently not used) + tape.random_seed = replay->seed; - return data_decoded; -} + TapeSetDateFromIsoDateString(replay->date); -static int getMappedElement_DC(int element) -{ - switch (element) + tape.counter = 0; + tape.pos[tape.counter].delay = 0; + + tape.bd_replay = TRUE; + + // all time calculations only used to display approximate tape time + int cave_speed = cave->speed; + int milliseconds_game = 0; + int milliseconds_elapsed = 20; + + for (i = 0; i < replay->movements->len; i++) { - case 0x0000: - element = EL_ROCK; - break; + int replay_action = replay->movements->data[i]; + int tape_action = map_action_BD_to_RND(replay_action); + byte action[MAX_TAPE_ACTIONS] = { tape_action }; + boolean success = 0; - // 0x0117 - 0x036e: (?) - // EL_DIAMOND + while (1) + { + success = TapeAddAction(action); - // 0x042d - 0x0684: (?) - // EL_EMERALD + milliseconds_game += milliseconds_elapsed; - case 0x06f1: - element = EL_NUT; - break; + if (milliseconds_game >= cave_speed) + { + milliseconds_game -= cave_speed; - case 0x074c: - element = EL_BOMB; - break; + break; + } + } - case 0x07a4: - element = EL_PEARL; - break; + tape.counter++; + tape.pos[tape.counter].delay = 0; + tape.pos[tape.counter].action[0] = 0; - case 0x0823: - element = EL_CRYSTAL; - break; + if (!success) + { + Warn("BD replay truncated: size exceeds maximum tape size %d", MAX_TAPE_LEN); - case 0x0e77: // quicksand (boulder) - element = EL_QUICKSAND_FAST_FULL; break; + } + } - case 0x0e99: // slow quicksand (boulder) - element = EL_QUICKSAND_FULL; - break; + TapeHaltRecording(); - case 0x0ed2: - element = EL_EM_EXIT_OPEN; - break; + if (!replay->success) + Warn("BD replay is marked as not successful"); +} - case 0x0ee3: - element = EL_EM_EXIT_CLOSED; - break; - case 0x0eeb: - element = EL_EM_STEEL_EXIT_OPEN; - break; +// ---------------------------------------------------------------------------- +// functions for loading EM level +// ---------------------------------------------------------------------------- - case 0x0efc: - element = EL_EM_STEEL_EXIT_CLOSED; - break; +static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) +{ + static int ball_xy[8][2] = + { + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 0, 1 }, + { 2, 1 }, + { 0, 2 }, + { 1, 2 }, + { 2, 2 }, + }; + struct LevelInfo_EM *level_em = level->native_em_level; + struct CAVE *cav = level_em->cav; + int i, j, x, y; - case 0x0f4f: // dynamite (lit 1) - element = EL_EM_DYNAMITE_ACTIVE; - break; + cav->width = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH); + cav->height = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT); - case 0x0f57: // dynamite (lit 2) - element = EL_EM_DYNAMITE_ACTIVE; - break; + cav->time_seconds = level->time; + cav->gems_needed = level->gems_needed; - case 0x0f5f: // dynamite (lit 3) - element = EL_EM_DYNAMITE_ACTIVE; - break; + cav->emerald_score = level->score[SC_EMERALD]; + cav->diamond_score = level->score[SC_DIAMOND]; + cav->alien_score = level->score[SC_ROBOT]; + cav->tank_score = level->score[SC_SPACESHIP]; + cav->bug_score = level->score[SC_BUG]; + cav->eater_score = level->score[SC_YAMYAM]; + cav->nut_score = level->score[SC_NUT]; + cav->dynamite_score = level->score[SC_DYNAMITE]; + cav->key_score = level->score[SC_KEY]; + cav->exit_score = level->score[SC_TIME_BONUS]; - case 0x0f67: // dynamite (lit 4) - element = EL_EM_DYNAMITE_ACTIVE; - break; + cav->num_eater_arrays = level->num_yamyam_contents; - case 0x0f81: - case 0x0f82: - case 0x0f83: - case 0x0f84: - element = EL_AMOEBA_WET; - break; + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + cav->eater_array[i][y * 3 + x] = + map_element_RND_to_EM_cave(level->yamyam_content[i].e[x][y]); - case 0x0f85: - element = EL_AMOEBA_DROP; - break; + cav->amoeba_time = level->amoeba_speed; + cav->wonderwall_time = level->time_magic_wall; + cav->wheel_time = level->time_wheel; - case 0x0fb9: - element = EL_DC_MAGIC_WALL; - break; + cav->android_move_time = level->android_move_time; + cav->android_clone_time = level->android_clone_time; + cav->ball_random = level->ball_random; + cav->ball_active = level->ball_active_initial; + cav->ball_time = level->ball_time; + cav->num_ball_arrays = level->num_ball_contents; - case 0x0fd0: - element = EL_SPACESHIP_UP; - break; + cav->lenses_score = level->lenses_score; + cav->magnify_score = level->magnify_score; + cav->slurp_score = level->slurp_score; - case 0x0fd9: - element = EL_SPACESHIP_DOWN; - break; + cav->lenses_time = level->lenses_time; + cav->magnify_time = level->magnify_time; - case 0x0ff1: - element = EL_SPACESHIP_LEFT; - break; + cav->wind_time = 9999; + cav->wind_direction = + map_direction_RND_to_EM(level->wind_direction_initial); - case 0x0ff9: - element = EL_SPACESHIP_RIGHT; - break; + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (j = 0; j < 8; j++) + cav->ball_array[i][j] = + map_element_RND_to_EM_cave(level->ball_content[i]. + e[ball_xy[j][0]][ball_xy[j][1]]); - case 0x1057: - element = EL_BUG_UP; - break; + map_android_clone_elements_RND_to_EM(level); - case 0x1060: - element = EL_BUG_DOWN; - break; + // first fill the complete playfield with the empty space element + for (y = 0; y < EM_MAX_CAVE_HEIGHT; y++) + for (x = 0; x < EM_MAX_CAVE_WIDTH; x++) + cav->cave[x][y] = Cblank; - case 0x1078: - element = EL_BUG_LEFT; - break; + // then copy the real level contents from level file into the playfield + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) + { + int new_element = map_element_RND_to_EM_cave(level->field[x][y]); + + if (level->field[x][y] == EL_AMOEBA_DEAD) + new_element = map_element_RND_to_EM_cave(EL_AMOEBA_WET); + + cav->cave[x][y] = new_element; + } - case 0x1080: - element = EL_BUG_RIGHT; - break; + for (i = 0; i < MAX_PLAYERS; i++) + { + cav->player_x[i] = -1; + cav->player_y[i] = -1; + } - case 0x10de: - element = EL_MOLE_UP; - break; + // initialize player positions and delete players from the playfield + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) + { + if (IS_PLAYER_ELEMENT(level->field[x][y])) + { + int player_nr = GET_PLAYER_NR(level->field[x][y]); - case 0x10e7: - element = EL_MOLE_DOWN; - break; + cav->player_x[player_nr] = x; + cav->player_y[player_nr] = y; - case 0x10ff: - element = EL_MOLE_LEFT; - break; + cav->cave[x][y] = map_element_RND_to_EM_cave(EL_EMPTY); + } + } +} - case 0x1107: - element = EL_MOLE_RIGHT; - break; +static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) +{ + static int ball_xy[8][2] = + { + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 0, 1 }, + { 2, 1 }, + { 0, 2 }, + { 1, 2 }, + { 2, 2 }, + }; + struct LevelInfo_EM *level_em = level->native_em_level; + struct CAVE *cav = level_em->cav; + int i, j, x, y; - case 0x11c0: - element = EL_ROBOT; - break; + level->fieldx = MIN(cav->width, MAX_LEV_FIELDX); + level->fieldy = MIN(cav->height, MAX_LEV_FIELDY); - case 0x13f5: - element = EL_YAMYAM_UP; - break; + level->time = cav->time_seconds; + level->gems_needed = cav->gems_needed; - case 0x1425: - element = EL_SWITCHGATE_OPEN; - break; + sprintf(level->name, "Level %d", level->file_info.nr); - case 0x1426: - element = EL_SWITCHGATE_CLOSED; - break; + level->score[SC_EMERALD] = cav->emerald_score; + level->score[SC_DIAMOND] = cav->diamond_score; + level->score[SC_ROBOT] = cav->alien_score; + level->score[SC_SPACESHIP] = cav->tank_score; + level->score[SC_BUG] = cav->bug_score; + level->score[SC_YAMYAM] = cav->eater_score; + level->score[SC_NUT] = cav->nut_score; + level->score[SC_DYNAMITE] = cav->dynamite_score; + level->score[SC_KEY] = cav->key_score; + level->score[SC_TIME_BONUS] = cav->exit_score; - case 0x1437: - element = EL_DC_SWITCHGATE_SWITCH_UP; - break; + level->num_yamyam_contents = cav->num_eater_arrays; - case 0x143a: - element = EL_TIMEGATE_CLOSED; - break; + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + level->yamyam_content[i].e[x][y] = + map_element_EM_to_RND_cave(cav->eater_array[i][y * 3 + x]); - case 0x144c: // conveyor belt switch (green) - element = EL_CONVEYOR_BELT_3_SWITCH_MIDDLE; - break; + level->amoeba_speed = cav->amoeba_time; + level->time_magic_wall = cav->wonderwall_time; + level->time_wheel = cav->wheel_time; - case 0x144f: // conveyor belt switch (red) - element = EL_CONVEYOR_BELT_1_SWITCH_MIDDLE; - break; + level->android_move_time = cav->android_move_time; + level->android_clone_time = cav->android_clone_time; + level->ball_random = cav->ball_random; + level->ball_active_initial = cav->ball_active; + level->ball_time = cav->ball_time; + level->num_ball_contents = cav->num_ball_arrays; - case 0x1452: // conveyor belt switch (blue) - element = EL_CONVEYOR_BELT_4_SWITCH_MIDDLE; - break; + level->lenses_score = cav->lenses_score; + level->magnify_score = cav->magnify_score; + level->slurp_score = cav->slurp_score; - case 0x145b: - element = EL_CONVEYOR_BELT_3_MIDDLE; - break; + level->lenses_time = cav->lenses_time; + level->magnify_time = cav->magnify_time; - case 0x1463: - element = EL_CONVEYOR_BELT_3_LEFT; - break; + level->wind_direction_initial = + map_direction_EM_to_RND(cav->wind_direction); - case 0x146b: - element = EL_CONVEYOR_BELT_3_RIGHT; - break; + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (j = 0; j < 8; j++) + level->ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]] = + map_element_EM_to_RND_cave(cav->ball_array[i][j]); - case 0x1473: - element = EL_CONVEYOR_BELT_1_MIDDLE; - break; + map_android_clone_elements_EM_to_RND(level); - case 0x147b: - element = EL_CONVEYOR_BELT_1_LEFT; - break; + // convert the playfield (some elements need special treatment) + for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++) + { + int new_element = map_element_EM_to_RND_cave(cav->cave[x][y]); - case 0x1483: - element = EL_CONVEYOR_BELT_1_RIGHT; - break; + if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0) + new_element = EL_AMOEBA_DEAD; - case 0x148b: - element = EL_CONVEYOR_BELT_4_MIDDLE; - break; + level->field[x][y] = new_element; + } - case 0x1493: - element = EL_CONVEYOR_BELT_4_LEFT; - break; + for (i = 0; i < MAX_PLAYERS; i++) + { + // in case of all players set to the same field, use the first player + int nr = MAX_PLAYERS - i - 1; + int jx = cav->player_x[nr]; + int jy = cav->player_y[nr]; - case 0x149b: - element = EL_CONVEYOR_BELT_4_RIGHT; - break; + if (jx != -1 && jy != -1) + level->field[jx][jy] = EL_PLAYER_1 + nr; + } - case 0x14ac: - element = EL_EXPANDABLE_WALL_HORIZONTAL; - break; + // time score is counted for each 10 seconds left in Emerald Mine levels + level->time_score_base = 10; +} - case 0x14bd: - element = EL_EXPANDABLE_WALL_VERTICAL; - break; - case 0x14c6: - element = EL_EXPANDABLE_WALL_ANY; - break; +// ---------------------------------------------------------------------------- +// functions for loading SP level +// ---------------------------------------------------------------------------- - case 0x14ce: // growing steel wall (left/right) - element = EL_EXPANDABLE_STEELWALL_HORIZONTAL; - break; +static void CopyNativeLevel_RND_to_SP(struct LevelInfo *level) +{ + struct LevelInfo_SP *level_sp = level->native_sp_level; + LevelInfoType *header = &level_sp->header; + int i, x, y; - case 0x14df: // growing steel wall (up/down) - element = EL_EXPANDABLE_STEELWALL_VERTICAL; - break; + level_sp->width = level->fieldx; + level_sp->height = level->fieldy; - case 0x14e8: // growing steel wall (up/down/left/right) - element = EL_EXPANDABLE_STEELWALL_ANY; - break; + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + level_sp->playfield[x][y] = map_element_RND_to_SP(level->field[x][y]); - case 0x14e9: - element = EL_SHIELD_DEADLY; - break; + header->InitialGravity = (level->initial_player_gravity[0] ? 1 : 0); - case 0x1501: - element = EL_EXTRA_TIME; - break; + for (i = 0; i < SP_LEVEL_NAME_LEN; i++) + header->LevelTitle[i] = level->name[i]; + // !!! NO STRING TERMINATION IN SUPAPLEX VB CODE YET -- FIX THIS !!! - case 0x154f: - element = EL_ACID; - break; + header->InfotronsNeeded = level->gems_needed; - case 0x1577: - element = EL_EMPTY_SPACE; - break; + header->SpecialPortCount = 0; - case 0x1578: // quicksand (empty) - element = EL_QUICKSAND_FAST_EMPTY; - break; + for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++) + { + boolean gravity_port_found = FALSE; + boolean gravity_port_valid = FALSE; + int gravity_port_flag; + int gravity_port_base_element; + int element = level->field[x][y]; - case 0x1579: // slow quicksand (empty) - element = EL_QUICKSAND_EMPTY; - break; + if (element >= EL_SP_GRAVITY_ON_PORT_RIGHT && + element <= EL_SP_GRAVITY_ON_PORT_UP) + { + gravity_port_found = TRUE; + gravity_port_valid = TRUE; + gravity_port_flag = 1; + gravity_port_base_element = EL_SP_GRAVITY_ON_PORT_RIGHT; + } + else if (element >= EL_SP_GRAVITY_OFF_PORT_RIGHT && + element <= EL_SP_GRAVITY_OFF_PORT_UP) + { + gravity_port_found = TRUE; + gravity_port_valid = TRUE; + gravity_port_flag = 0; + gravity_port_base_element = EL_SP_GRAVITY_OFF_PORT_RIGHT; + } + else if (element >= EL_SP_GRAVITY_PORT_RIGHT && + element <= EL_SP_GRAVITY_PORT_UP) + { + // change R'n'D style gravity inverting special port to normal port + // (there are no gravity inverting ports in native Supaplex engine) - // 0x157c - 0x158b: - // EL_SAND + gravity_port_found = TRUE; + gravity_port_valid = FALSE; + gravity_port_base_element = EL_SP_GRAVITY_PORT_RIGHT; + } - // 0x1590 - 0x159f: - // EL_DC_LANDMINE + if (gravity_port_found) + { + if (gravity_port_valid && + header->SpecialPortCount < SP_MAX_SPECIAL_PORTS) + { + SpecialPortType *port = &header->SpecialPort[header->SpecialPortCount]; - case 0x15a0: - element = EL_EM_DYNAMITE; - break; + port->PortLocation = (y * level->fieldx + x) * 2; + port->Gravity = gravity_port_flag; - case 0x15a1: // key (red) - element = EL_EM_KEY_1; - break; + element += EL_SP_GRAVITY_PORT_RIGHT - gravity_port_base_element; - case 0x15a2: // key (yellow) - element = EL_EM_KEY_2; - break; + header->SpecialPortCount++; + } + else + { + // change special gravity port to normal port - case 0x15a3: // key (blue) - element = EL_EM_KEY_4; - break; + element += EL_SP_PORT_RIGHT - gravity_port_base_element; + } - case 0x15a4: // key (green) - element = EL_EM_KEY_3; - break; + level_sp->playfield[x][y] = element - EL_SP_START; + } + } +} - case 0x15a5: // key (white) - element = EL_DC_KEY_WHITE; - break; +static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_SP *level_sp = level->native_sp_level; + LevelInfoType *header = &level_sp->header; + boolean num_invalid_elements = 0; + int i, j, x, y; - case 0x15a6: - element = EL_WALL_SLIPPERY; - break; + level->fieldx = level_sp->width; + level->fieldy = level_sp->height; - case 0x15a7: - element = EL_WALL; - break; + for (x = 0; x < level->fieldx; x++) + { + for (y = 0; y < level->fieldy; y++) + { + int element_old = level_sp->playfield[x][y]; + int element_new = getMappedElement(map_element_SP_to_RND(element_old)); - case 0x15a8: // wall (not round) - element = EL_WALL; - break; + if (element_new == EL_UNKNOWN) + { + num_invalid_elements++; - case 0x15a9: // (blue) - element = EL_CHAR_A; - break; + Debug("level:native:SP", "invalid element %d at position %d, %d", + element_old, x, y); + } - case 0x15aa: // (blue) - element = EL_CHAR_B; - break; + level->field[x][y] = element_new; + } + } - case 0x15ab: // (blue) - element = EL_CHAR_C; - break; + if (num_invalid_elements > 0) + Warn("found %d invalid elements%s", num_invalid_elements, + (!options.debug ? " (use '--debug' for more details)" : "")); - case 0x15ac: // (blue) - element = EL_CHAR_D; - break; + for (i = 0; i < MAX_PLAYERS; i++) + level->initial_player_gravity[i] = + (header->InitialGravity == 1 ? TRUE : FALSE); - case 0x15ad: // (blue) - element = EL_CHAR_E; + // skip leading spaces + for (i = 0; i < SP_LEVEL_NAME_LEN; i++) + if (header->LevelTitle[i] != ' ') break; - case 0x15ae: // (blue) - element = EL_CHAR_F; - break; + // copy level title + for (j = 0; i < SP_LEVEL_NAME_LEN; i++, j++) + level->name[j] = header->LevelTitle[i]; + level->name[j] = '\0'; - case 0x15af: // (blue) - element = EL_CHAR_G; - break; + // cut trailing spaces + for (; j > 0; j--) + if (level->name[j - 1] == ' ' && level->name[j] == '\0') + level->name[j - 1] = '\0'; - case 0x15b0: // (blue) - element = EL_CHAR_H; - break; + level->gems_needed = header->InfotronsNeeded; - case 0x15b1: // (blue) - element = EL_CHAR_I; - break; + for (i = 0; i < header->SpecialPortCount; i++) + { + SpecialPortType *port = &header->SpecialPort[i]; + int port_location = port->PortLocation; + int gravity = port->Gravity; + int port_x, port_y, port_element; - case 0x15b2: // (blue) - element = EL_CHAR_J; - break; + port_x = (port_location / 2) % level->fieldx; + port_y = (port_location / 2) / level->fieldx; - case 0x15b3: // (blue) - element = EL_CHAR_K; - break; + if (port_x < 0 || port_x >= level->fieldx || + port_y < 0 || port_y >= level->fieldy) + { + Warn("special port position (%d, %d) out of bounds", port_x, port_y); - case 0x15b4: // (blue) - element = EL_CHAR_L; - break; + continue; + } - case 0x15b5: // (blue) - element = EL_CHAR_M; - break; + port_element = level->field[port_x][port_y]; - case 0x15b6: // (blue) - element = EL_CHAR_N; - break; + if (port_element < EL_SP_GRAVITY_PORT_RIGHT || + port_element > EL_SP_GRAVITY_PORT_UP) + { + Warn("no special port at position (%d, %d)", port_x, port_y); - case 0x15b7: // (blue) - element = EL_CHAR_O; - break; + continue; + } - case 0x15b8: // (blue) - element = EL_CHAR_P; - break; + // 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; + } - case 0x15b9: // (blue) - element = EL_CHAR_Q; - break; + // change special gravity ports without database entries to normal ports + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + 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; - case 0x15ba: // (blue) - element = EL_CHAR_R; - break; + level->time = 0; // no time limit + level->amoeba_speed = 0; + level->time_magic_wall = 0; + level->time_wheel = 0; + level->amoeba_content = EL_EMPTY; - case 0x15bb: // (blue) - element = EL_CHAR_S; - break; + // original Supaplex does not use score values -- rate by playing time + for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) + level->score[i] = 0; - case 0x15bc: // (blue) - element = EL_CHAR_T; - break; + level->rate_time_over_score = TRUE; - case 0x15bd: // (blue) - element = EL_CHAR_U; - break; + // there are no yamyams in supaplex levels + for (i = 0; i < level->num_yamyam_contents; i++) + for (x = 0; x < 3; x++) + for (y = 0; y < 3; y++) + level->yamyam_content[i].e[x][y] = EL_EMPTY; +} - case 0x15be: // (blue) - element = EL_CHAR_V; - break; +static void CopyNativeTape_RND_to_SP(struct LevelInfo *level) +{ + struct LevelInfo_SP *level_sp = level->native_sp_level; + struct DemoInfo_SP *demo = &level_sp->demo; + int i, j; - case 0x15bf: // (blue) - element = EL_CHAR_W; - break; + // always start with reliable default values + demo->is_available = FALSE; + demo->length = 0; - case 0x15c0: // (blue) - element = EL_CHAR_X; - break; + if (TAPE_IS_EMPTY(tape)) + return; - case 0x15c1: // (blue) - element = EL_CHAR_Y; - break; + demo->level_nr = tape.level_nr; // (currently not used) - case 0x15c2: // (blue) - element = EL_CHAR_Z; - break; + level_sp->header.DemoRandomSeed = tape.random_seed; - case 0x15c3: // (blue) - element = EL_CHAR_AUMLAUT; - break; + demo->length = 0; - case 0x15c4: // (blue) - element = EL_CHAR_OUMLAUT; - break; + for (i = 0; i < tape.length; i++) + { + int demo_action = map_key_RND_to_SP(tape.pos[i].action[0]); + int demo_repeat = tape.pos[i].delay; + int demo_entries = (demo_repeat + 15) / 16; - case 0x15c5: // (blue) - element = EL_CHAR_UUMLAUT; - break; + if (demo->length + demo_entries >= SP_MAX_TAPE_LEN) + { + Warn("tape truncated: size exceeds maximum SP demo size %d", + SP_MAX_TAPE_LEN); - case 0x15c6: // (blue) - element = EL_CHAR_0; break; + } - case 0x15c7: // (blue) - element = EL_CHAR_1; - break; + for (j = 0; j < demo_repeat / 16; j++) + demo->data[demo->length++] = 0xf0 | demo_action; - case 0x15c8: // (blue) - element = EL_CHAR_2; - break; + if (demo_repeat % 16) + demo->data[demo->length++] = ((demo_repeat % 16 - 1) << 4) | demo_action; + } - case 0x15c9: // (blue) - element = EL_CHAR_3; - break; + demo->is_available = TRUE; +} - case 0x15ca: // (blue) - element = EL_CHAR_4; - break; +static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_SP *level_sp = level->native_sp_level; + struct DemoInfo_SP *demo = &level_sp->demo; + char *filename = level->file_info.filename; + int i; - case 0x15cb: // (blue) - element = EL_CHAR_5; - break; + // always start with reliable default values + setTapeInfoToDefaults(); - case 0x15cc: // (blue) - element = EL_CHAR_6; - break; + if (!demo->is_available) + return; - case 0x15cd: // (blue) - element = EL_CHAR_7; - break; + tape.level_nr = demo->level_nr; // (currently not used) + tape.random_seed = level_sp->header.DemoRandomSeed; - case 0x15ce: // (blue) - element = EL_CHAR_8; - break; + TapeSetDateFromEpochSeconds(getFileTimestampEpochSeconds(filename)); - case 0x15cf: // (blue) - element = EL_CHAR_9; - break; + tape.counter = 0; + tape.pos[tape.counter].delay = 0; - case 0x15d0: // (blue) - element = EL_CHAR_PERIOD; - break; + for (i = 0; i < demo->length; i++) + { + int demo_action = demo->data[i] & 0x0f; + int demo_repeat = (demo->data[i] & 0xf0) >> 4; + int tape_action = map_key_SP_to_RND(demo_action); + int tape_repeat = demo_repeat + 1; + byte action[MAX_TAPE_ACTIONS] = { tape_action }; + boolean success = 0; + int j; - case 0x15d1: // (blue) - element = EL_CHAR_EXCLAM; - break; + for (j = 0; j < tape_repeat; j++) + success = TapeAddAction(action); - case 0x15d2: // (blue) - element = EL_CHAR_COLON; - break; + if (!success) + { + Warn("SP demo truncated: size exceeds maximum tape size %d", + MAX_TAPE_LEN); - case 0x15d3: // (blue) - element = EL_CHAR_LESS; break; + } + } - case 0x15d4: // (blue) - element = EL_CHAR_GREATER; - break; + TapeHaltRecording(); +} - case 0x15d5: // (blue) - element = EL_CHAR_QUESTION; - break; - case 0x15d6: // (blue) - element = EL_CHAR_COPYRIGHT; - break; +// ---------------------------------------------------------------------------- +// functions for loading MM level +// ---------------------------------------------------------------------------- - case 0x15d7: // (blue) - element = EL_CHAR_UP; - break; +static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) +{ + struct LevelInfo_MM *level_mm = level->native_mm_level; + int i, x, y; - case 0x15d8: // (blue) - element = EL_CHAR_DOWN; - break; + level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH); + level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT); - case 0x15d9: // (blue) - element = EL_CHAR_BUTTON; - break; + level_mm->time = level->time; + level_mm->kettles_needed = level->gems_needed; + level_mm->auto_count_kettles = level->auto_count_gems; - case 0x15da: // (blue) - element = EL_CHAR_PLUS; - break; + level_mm->mm_laser_red = level->mm_laser_red; + level_mm->mm_laser_green = level->mm_laser_green; + level_mm->mm_laser_blue = level->mm_laser_blue; - case 0x15db: // (blue) - element = EL_CHAR_MINUS; - break; + level_mm->df_laser_red = level->df_laser_red; + level_mm->df_laser_green = level->df_laser_green; + level_mm->df_laser_blue = level->df_laser_blue; - case 0x15dc: // (blue) - element = EL_CHAR_APOSTROPHE; - break; + strcpy(level_mm->name, level->name); + strcpy(level_mm->author, level->author); - case 0x15dd: // (blue) - element = EL_CHAR_PARENLEFT; - break; + level_mm->score[SC_EMERALD] = level->score[SC_EMERALD]; + level_mm->score[SC_PACMAN] = level->score[SC_PACMAN]; + level_mm->score[SC_KEY] = level->score[SC_KEY]; + level_mm->score[SC_TIME_BONUS] = level->score[SC_TIME_BONUS]; + level_mm->score[SC_ELEM_BONUS] = level->score[SC_ELEM_BONUS]; - case 0x15de: // (blue) - element = EL_CHAR_PARENRIGHT; - break; + level_mm->amoeba_speed = level->amoeba_speed; + level_mm->time_fuse = level->mm_time_fuse; + level_mm->time_bomb = level->mm_time_bomb; + level_mm->time_ball = level->mm_time_ball; + level_mm->time_block = level->mm_time_block; - case 0x15df: // (green) - element = EL_CHAR_A; - break; + level_mm->num_ball_contents = level->num_mm_ball_contents; + level_mm->ball_choice_mode = level->mm_ball_choice_mode; + level_mm->rotate_ball_content = level->rotate_mm_ball_content; + level_mm->explode_ball = level->explode_mm_ball; - case 0x15e0: // (green) - element = EL_CHAR_B; - break; + for (i = 0; i < level->num_mm_ball_contents; i++) + level_mm->ball_content[i] = + map_element_RND_to_MM(level->mm_ball_content[i]); - case 0x15e1: // (green) - element = EL_CHAR_C; - break; + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + Ur[x][y] = + level_mm->field[x][y] = map_element_RND_to_MM(level->field[x][y]); +} - case 0x15e2: // (green) - element = EL_CHAR_D; - break; +static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_MM *level_mm = level->native_mm_level; + int i, x, y; - case 0x15e3: // (green) - element = EL_CHAR_E; - break; + level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX); + level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY); - case 0x15e4: // (green) - element = EL_CHAR_F; - break; + level->time = level_mm->time; + level->gems_needed = level_mm->kettles_needed; + level->auto_count_gems = level_mm->auto_count_kettles; - case 0x15e5: // (green) - element = EL_CHAR_G; - break; + level->mm_laser_red = level_mm->mm_laser_red; + level->mm_laser_green = level_mm->mm_laser_green; + level->mm_laser_blue = level_mm->mm_laser_blue; - case 0x15e6: // (green) - element = EL_CHAR_H; - break; + level->df_laser_red = level_mm->df_laser_red; + level->df_laser_green = level_mm->df_laser_green; + level->df_laser_blue = level_mm->df_laser_blue; - case 0x15e7: // (green) - element = EL_CHAR_I; - break; + strcpy(level->name, level_mm->name); - case 0x15e8: // (green) - element = EL_CHAR_J; - break; + // only overwrite author from 'levelinfo.conf' if author defined in level + if (!strEqual(level_mm->author, ANONYMOUS_NAME)) + strcpy(level->author, level_mm->author); - case 0x15e9: // (green) - element = EL_CHAR_K; - break; + level->score[SC_EMERALD] = level_mm->score[SC_EMERALD]; + level->score[SC_PACMAN] = level_mm->score[SC_PACMAN]; + level->score[SC_KEY] = level_mm->score[SC_KEY]; + level->score[SC_TIME_BONUS] = level_mm->score[SC_TIME_BONUS]; + level->score[SC_ELEM_BONUS] = level_mm->score[SC_ELEM_BONUS]; - case 0x15ea: // (green) - element = EL_CHAR_L; - break; + level->amoeba_speed = level_mm->amoeba_speed; + level->mm_time_fuse = level_mm->time_fuse; + level->mm_time_bomb = level_mm->time_bomb; + level->mm_time_ball = level_mm->time_ball; + level->mm_time_block = level_mm->time_block; - case 0x15eb: // (green) - element = EL_CHAR_M; - break; + level->num_mm_ball_contents = level_mm->num_ball_contents; + level->mm_ball_choice_mode = level_mm->ball_choice_mode; + level->rotate_mm_ball_content = level_mm->rotate_ball_content; + level->explode_mm_ball = level_mm->explode_ball; - case 0x15ec: // (green) - element = EL_CHAR_N; - break; + for (i = 0; i < level->num_mm_ball_contents; i++) + level->mm_ball_content[i] = + map_element_MM_to_RND(level_mm->ball_content[i]); - case 0x15ed: // (green) - element = EL_CHAR_O; - break; + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]); +} - case 0x15ee: // (green) - element = EL_CHAR_P; - break; - case 0x15ef: // (green) - element = EL_CHAR_Q; - break; +// ---------------------------------------------------------------------------- +// functions for loading DC level +// ---------------------------------------------------------------------------- - case 0x15f0: // (green) - element = EL_CHAR_R; - break; +#define DC_LEVEL_HEADER_SIZE 344 - case 0x15f1: // (green) - element = EL_CHAR_S; - break; +static unsigned short getDecodedWord_DC(unsigned short data_encoded, + boolean init) +{ + static int last_data_encoded; + static int offset1; + static int offset2; + int diff; + int diff_hi, diff_lo; + int data_hi, data_lo; + unsigned short data_decoded; - case 0x15f2: // (green) - element = EL_CHAR_T; - break; + if (init) + { + last_data_encoded = 0; + offset1 = -1; + offset2 = 0; - case 0x15f3: // (green) - element = EL_CHAR_U; - break; + return 0; + } - case 0x15f4: // (green) - element = EL_CHAR_V; - break; + diff = data_encoded - last_data_encoded; + diff_hi = diff & ~0xff; + diff_lo = diff & 0xff; - case 0x15f5: // (green) - element = EL_CHAR_W; - break; + offset2 += diff_lo; - case 0x15f6: // (green) - element = EL_CHAR_X; - break; + data_hi = diff_hi - (offset1 << 8) + (offset2 & 0xff00); + data_lo = (diff_lo + (data_hi >> 16)) & 0x00ff; + data_hi = data_hi & 0xff00; - case 0x15f7: // (green) - element = EL_CHAR_Y; - break; + data_decoded = data_hi | data_lo; - case 0x15f8: // (green) - element = EL_CHAR_Z; - break; + last_data_encoded = data_encoded; - case 0x15f9: // (green) - element = EL_CHAR_AUMLAUT; - break; + offset1 = (offset1 + 1) % 31; + offset2 = offset2 & 0xff; - case 0x15fa: // (green) - element = EL_CHAR_OUMLAUT; - break; + return data_decoded; +} - case 0x15fb: // (green) - element = EL_CHAR_UUMLAUT; +static int getMappedElement_DC(int element) +{ + switch (element) + { + case 0x0000: + element = EL_ROCK; break; - case 0x15fc: // (green) - element = EL_CHAR_0; - break; + // 0x0117 - 0x036e: (?) + // EL_DIAMOND - case 0x15fd: // (green) - element = EL_CHAR_1; - break; + // 0x042d - 0x0684: (?) + // EL_EMERALD - case 0x15fe: // (green) - element = EL_CHAR_2; + case 0x06f1: + element = EL_NUT; break; - case 0x15ff: // (green) - element = EL_CHAR_3; + case 0x074c: + element = EL_BOMB; break; - case 0x1600: // (green) - element = EL_CHAR_4; + case 0x07a4: + element = EL_PEARL; break; - case 0x1601: // (green) - element = EL_CHAR_5; + case 0x0823: + element = EL_CRYSTAL; break; - case 0x1602: // (green) - element = EL_CHAR_6; + case 0x0e77: // quicksand (boulder) + element = EL_QUICKSAND_FAST_FULL; break; - case 0x1603: // (green) - element = EL_CHAR_7; + case 0x0e99: // slow quicksand (boulder) + element = EL_QUICKSAND_FULL; break; - case 0x1604: // (green) - element = EL_CHAR_8; + case 0x0ed2: + element = EL_EM_EXIT_OPEN; break; - case 0x1605: // (green) - element = EL_CHAR_9; + case 0x0ee3: + element = EL_EM_EXIT_CLOSED; break; - case 0x1606: // (green) - element = EL_CHAR_PERIOD; + case 0x0eeb: + element = EL_EM_STEEL_EXIT_OPEN; break; - case 0x1607: // (green) - element = EL_CHAR_EXCLAM; + case 0x0efc: + element = EL_EM_STEEL_EXIT_CLOSED; break; - case 0x1608: // (green) - element = EL_CHAR_COLON; + case 0x0f4f: // dynamite (lit 1) + element = EL_EM_DYNAMITE_ACTIVE; break; - case 0x1609: // (green) - element = EL_CHAR_LESS; + case 0x0f57: // dynamite (lit 2) + element = EL_EM_DYNAMITE_ACTIVE; break; - case 0x160a: // (green) - element = EL_CHAR_GREATER; + case 0x0f5f: // dynamite (lit 3) + element = EL_EM_DYNAMITE_ACTIVE; break; - case 0x160b: // (green) - element = EL_CHAR_QUESTION; + case 0x0f67: // dynamite (lit 4) + element = EL_EM_DYNAMITE_ACTIVE; break; - case 0x160c: // (green) - element = EL_CHAR_COPYRIGHT; + case 0x0f81: + case 0x0f82: + case 0x0f83: + case 0x0f84: + element = EL_AMOEBA_WET; break; - case 0x160d: // (green) - element = EL_CHAR_UP; + case 0x0f85: + element = EL_AMOEBA_DROP; break; - case 0x160e: // (green) - element = EL_CHAR_DOWN; + case 0x0fb9: + element = EL_DC_MAGIC_WALL; break; - case 0x160f: // (green) - element = EL_CHAR_BUTTON; + case 0x0fd0: + element = EL_SPACESHIP_UP; break; - case 0x1610: // (green) - element = EL_CHAR_PLUS; + case 0x0fd9: + element = EL_SPACESHIP_DOWN; break; - case 0x1611: // (green) - element = EL_CHAR_MINUS; + case 0x0ff1: + element = EL_SPACESHIP_LEFT; break; - case 0x1612: // (green) - element = EL_CHAR_APOSTROPHE; + case 0x0ff9: + element = EL_SPACESHIP_RIGHT; break; - case 0x1613: // (green) - element = EL_CHAR_PARENLEFT; + case 0x1057: + element = EL_BUG_UP; break; - case 0x1614: // (green) - element = EL_CHAR_PARENRIGHT; + case 0x1060: + element = EL_BUG_DOWN; break; - case 0x1615: // (blue steel) - element = EL_STEEL_CHAR_A; + case 0x1078: + element = EL_BUG_LEFT; break; - case 0x1616: // (blue steel) - element = EL_STEEL_CHAR_B; + case 0x1080: + element = EL_BUG_RIGHT; break; - case 0x1617: // (blue steel) - element = EL_STEEL_CHAR_C; + case 0x10de: + element = EL_MOLE_UP; break; - case 0x1618: // (blue steel) - element = EL_STEEL_CHAR_D; + case 0x10e7: + element = EL_MOLE_DOWN; break; - case 0x1619: // (blue steel) - element = EL_STEEL_CHAR_E; + case 0x10ff: + element = EL_MOLE_LEFT; break; - case 0x161a: // (blue steel) - element = EL_STEEL_CHAR_F; + case 0x1107: + element = EL_MOLE_RIGHT; break; - case 0x161b: // (blue steel) - element = EL_STEEL_CHAR_G; + case 0x11c0: + element = EL_ROBOT; break; - case 0x161c: // (blue steel) - element = EL_STEEL_CHAR_H; + case 0x13f5: + element = EL_YAMYAM_UP; break; - case 0x161d: // (blue steel) - element = EL_STEEL_CHAR_I; + case 0x1425: + element = EL_SWITCHGATE_OPEN; break; - case 0x161e: // (blue steel) - element = EL_STEEL_CHAR_J; + case 0x1426: + element = EL_SWITCHGATE_CLOSED; break; - case 0x161f: // (blue steel) - element = EL_STEEL_CHAR_K; + case 0x1437: + element = EL_DC_SWITCHGATE_SWITCH_UP; break; - case 0x1620: // (blue steel) - element = EL_STEEL_CHAR_L; + case 0x143a: + element = EL_TIMEGATE_CLOSED; break; - case 0x1621: // (blue steel) - element = EL_STEEL_CHAR_M; + case 0x144c: // conveyor belt switch (green) + element = EL_CONVEYOR_BELT_3_SWITCH_MIDDLE; break; - case 0x1622: // (blue steel) - element = EL_STEEL_CHAR_N; + case 0x144f: // conveyor belt switch (red) + element = EL_CONVEYOR_BELT_1_SWITCH_MIDDLE; break; - case 0x1623: // (blue steel) - element = EL_STEEL_CHAR_O; + case 0x1452: // conveyor belt switch (blue) + element = EL_CONVEYOR_BELT_4_SWITCH_MIDDLE; break; - case 0x1624: // (blue steel) - element = EL_STEEL_CHAR_P; + case 0x145b: + element = EL_CONVEYOR_BELT_3_MIDDLE; break; - case 0x1625: // (blue steel) - element = EL_STEEL_CHAR_Q; + case 0x1463: + element = EL_CONVEYOR_BELT_3_LEFT; break; - case 0x1626: // (blue steel) - element = EL_STEEL_CHAR_R; + case 0x146b: + element = EL_CONVEYOR_BELT_3_RIGHT; break; - case 0x1627: // (blue steel) - element = EL_STEEL_CHAR_S; + case 0x1473: + element = EL_CONVEYOR_BELT_1_MIDDLE; break; - case 0x1628: // (blue steel) - element = EL_STEEL_CHAR_T; + case 0x147b: + element = EL_CONVEYOR_BELT_1_LEFT; break; - case 0x1629: // (blue steel) - element = EL_STEEL_CHAR_U; + case 0x1483: + element = EL_CONVEYOR_BELT_1_RIGHT; break; - case 0x162a: // (blue steel) - element = EL_STEEL_CHAR_V; + case 0x148b: + element = EL_CONVEYOR_BELT_4_MIDDLE; break; - case 0x162b: // (blue steel) - element = EL_STEEL_CHAR_W; + case 0x1493: + element = EL_CONVEYOR_BELT_4_LEFT; break; - case 0x162c: // (blue steel) - element = EL_STEEL_CHAR_X; + case 0x149b: + element = EL_CONVEYOR_BELT_4_RIGHT; break; - case 0x162d: // (blue steel) - element = EL_STEEL_CHAR_Y; + case 0x14ac: + element = EL_EXPANDABLE_WALL_HORIZONTAL; break; - case 0x162e: // (blue steel) - element = EL_STEEL_CHAR_Z; + case 0x14bd: + element = EL_EXPANDABLE_WALL_VERTICAL; break; - case 0x162f: // (blue steel) - element = EL_STEEL_CHAR_AUMLAUT; + case 0x14c6: + element = EL_EXPANDABLE_WALL_ANY; break; - case 0x1630: // (blue steel) - element = EL_STEEL_CHAR_OUMLAUT; + case 0x14ce: // growing steel wall (left/right) + element = EL_EXPANDABLE_STEELWALL_HORIZONTAL; break; - case 0x1631: // (blue steel) - element = EL_STEEL_CHAR_UUMLAUT; + case 0x14df: // growing steel wall (up/down) + element = EL_EXPANDABLE_STEELWALL_VERTICAL; break; - case 0x1632: // (blue steel) - element = EL_STEEL_CHAR_0; + case 0x14e8: // growing steel wall (up/down/left/right) + element = EL_EXPANDABLE_STEELWALL_ANY; break; - case 0x1633: // (blue steel) - element = EL_STEEL_CHAR_1; + case 0x14e9: + element = EL_SHIELD_DEADLY; break; - case 0x1634: // (blue steel) - element = EL_STEEL_CHAR_2; + case 0x1501: + element = EL_EXTRA_TIME; break; - case 0x1635: // (blue steel) - element = EL_STEEL_CHAR_3; + case 0x154f: + element = EL_ACID; break; - case 0x1636: // (blue steel) - element = EL_STEEL_CHAR_4; + case 0x1577: + element = EL_EMPTY_SPACE; break; - case 0x1637: // (blue steel) - element = EL_STEEL_CHAR_5; + case 0x1578: // quicksand (empty) + element = EL_QUICKSAND_FAST_EMPTY; break; - case 0x1638: // (blue steel) - element = EL_STEEL_CHAR_6; + case 0x1579: // slow quicksand (empty) + element = EL_QUICKSAND_EMPTY; break; - case 0x1639: // (blue steel) - element = EL_STEEL_CHAR_7; - break; + // 0x157c - 0x158b: + // EL_SAND - case 0x163a: // (blue steel) - element = EL_STEEL_CHAR_8; + // 0x1590 - 0x159f: + // EL_DC_LANDMINE + + case 0x15a0: + element = EL_EM_DYNAMITE; break; - case 0x163b: // (blue steel) - element = EL_STEEL_CHAR_9; + case 0x15a1: // key (red) + element = EL_EM_KEY_1; break; - case 0x163c: // (blue steel) - element = EL_STEEL_CHAR_PERIOD; + case 0x15a2: // key (yellow) + element = EL_EM_KEY_2; break; - case 0x163d: // (blue steel) - element = EL_STEEL_CHAR_EXCLAM; + case 0x15a3: // key (blue) + element = EL_EM_KEY_4; break; - case 0x163e: // (blue steel) - element = EL_STEEL_CHAR_COLON; + case 0x15a4: // key (green) + element = EL_EM_KEY_3; break; - case 0x163f: // (blue steel) - element = EL_STEEL_CHAR_LESS; + case 0x15a5: // key (white) + element = EL_DC_KEY_WHITE; break; - case 0x1640: // (blue steel) - element = EL_STEEL_CHAR_GREATER; + case 0x15a6: + element = EL_WALL_SLIPPERY; break; - case 0x1641: // (blue steel) - element = EL_STEEL_CHAR_QUESTION; + case 0x15a7: + element = EL_WALL; break; - case 0x1642: // (blue steel) - element = EL_STEEL_CHAR_COPYRIGHT; + case 0x15a8: // wall (not round) + element = EL_WALL; break; - case 0x1643: // (blue steel) - element = EL_STEEL_CHAR_UP; + case 0x15a9: // (blue) + element = EL_CHAR_A; break; - case 0x1644: // (blue steel) - element = EL_STEEL_CHAR_DOWN; + case 0x15aa: // (blue) + element = EL_CHAR_B; break; - case 0x1645: // (blue steel) - element = EL_STEEL_CHAR_BUTTON; + case 0x15ab: // (blue) + element = EL_CHAR_C; break; - case 0x1646: // (blue steel) - element = EL_STEEL_CHAR_PLUS; + case 0x15ac: // (blue) + element = EL_CHAR_D; break; - case 0x1647: // (blue steel) - element = EL_STEEL_CHAR_MINUS; + case 0x15ad: // (blue) + element = EL_CHAR_E; break; - case 0x1648: // (blue steel) - element = EL_STEEL_CHAR_APOSTROPHE; + case 0x15ae: // (blue) + element = EL_CHAR_F; break; - case 0x1649: // (blue steel) - element = EL_STEEL_CHAR_PARENLEFT; + case 0x15af: // (blue) + element = EL_CHAR_G; break; - case 0x164a: // (blue steel) - element = EL_STEEL_CHAR_PARENRIGHT; + case 0x15b0: // (blue) + element = EL_CHAR_H; break; - case 0x164b: // (green steel) - element = EL_STEEL_CHAR_A; + case 0x15b1: // (blue) + element = EL_CHAR_I; break; - case 0x164c: // (green steel) - element = EL_STEEL_CHAR_B; + case 0x15b2: // (blue) + element = EL_CHAR_J; break; - case 0x164d: // (green steel) - element = EL_STEEL_CHAR_C; + case 0x15b3: // (blue) + element = EL_CHAR_K; break; - case 0x164e: // (green steel) - element = EL_STEEL_CHAR_D; + case 0x15b4: // (blue) + element = EL_CHAR_L; break; - case 0x164f: // (green steel) - element = EL_STEEL_CHAR_E; + case 0x15b5: // (blue) + element = EL_CHAR_M; break; - case 0x1650: // (green steel) - element = EL_STEEL_CHAR_F; + case 0x15b6: // (blue) + element = EL_CHAR_N; break; - case 0x1651: // (green steel) - element = EL_STEEL_CHAR_G; + case 0x15b7: // (blue) + element = EL_CHAR_O; break; - case 0x1652: // (green steel) - element = EL_STEEL_CHAR_H; + case 0x15b8: // (blue) + element = EL_CHAR_P; break; - case 0x1653: // (green steel) - element = EL_STEEL_CHAR_I; + case 0x15b9: // (blue) + element = EL_CHAR_Q; break; - case 0x1654: // (green steel) - element = EL_STEEL_CHAR_J; + case 0x15ba: // (blue) + element = EL_CHAR_R; break; - case 0x1655: // (green steel) - element = EL_STEEL_CHAR_K; + case 0x15bb: // (blue) + element = EL_CHAR_S; break; - case 0x1656: // (green steel) - element = EL_STEEL_CHAR_L; + case 0x15bc: // (blue) + element = EL_CHAR_T; break; - case 0x1657: // (green steel) - element = EL_STEEL_CHAR_M; + case 0x15bd: // (blue) + element = EL_CHAR_U; break; - case 0x1658: // (green steel) - element = EL_STEEL_CHAR_N; + case 0x15be: // (blue) + element = EL_CHAR_V; break; - case 0x1659: // (green steel) - element = EL_STEEL_CHAR_O; + case 0x15bf: // (blue) + element = EL_CHAR_W; break; - case 0x165a: // (green steel) - element = EL_STEEL_CHAR_P; + case 0x15c0: // (blue) + element = EL_CHAR_X; break; - case 0x165b: // (green steel) - element = EL_STEEL_CHAR_Q; + case 0x15c1: // (blue) + element = EL_CHAR_Y; break; - case 0x165c: // (green steel) - element = EL_STEEL_CHAR_R; + case 0x15c2: // (blue) + element = EL_CHAR_Z; break; - case 0x165d: // (green steel) - element = EL_STEEL_CHAR_S; + case 0x15c3: // (blue) + element = EL_CHAR_AUMLAUT; break; - case 0x165e: // (green steel) - element = EL_STEEL_CHAR_T; + case 0x15c4: // (blue) + element = EL_CHAR_OUMLAUT; break; - case 0x165f: // (green steel) - element = EL_STEEL_CHAR_U; + case 0x15c5: // (blue) + element = EL_CHAR_UUMLAUT; break; - case 0x1660: // (green steel) - element = EL_STEEL_CHAR_V; + case 0x15c6: // (blue) + element = EL_CHAR_0; break; - case 0x1661: // (green steel) - element = EL_STEEL_CHAR_W; + case 0x15c7: // (blue) + element = EL_CHAR_1; break; - case 0x1662: // (green steel) - element = EL_STEEL_CHAR_X; + case 0x15c8: // (blue) + element = EL_CHAR_2; break; - case 0x1663: // (green steel) - element = EL_STEEL_CHAR_Y; + case 0x15c9: // (blue) + element = EL_CHAR_3; break; - case 0x1664: // (green steel) - element = EL_STEEL_CHAR_Z; + case 0x15ca: // (blue) + element = EL_CHAR_4; break; - case 0x1665: // (green steel) - element = EL_STEEL_CHAR_AUMLAUT; + case 0x15cb: // (blue) + element = EL_CHAR_5; break; - case 0x1666: // (green steel) - element = EL_STEEL_CHAR_OUMLAUT; + case 0x15cc: // (blue) + element = EL_CHAR_6; break; - case 0x1667: // (green steel) - element = EL_STEEL_CHAR_UUMLAUT; + case 0x15cd: // (blue) + element = EL_CHAR_7; break; - case 0x1668: // (green steel) - element = EL_STEEL_CHAR_0; + case 0x15ce: // (blue) + element = EL_CHAR_8; break; - case 0x1669: // (green steel) - element = EL_STEEL_CHAR_1; + case 0x15cf: // (blue) + element = EL_CHAR_9; break; - case 0x166a: // (green steel) - element = EL_STEEL_CHAR_2; + case 0x15d0: // (blue) + element = EL_CHAR_PERIOD; break; - case 0x166b: // (green steel) - element = EL_STEEL_CHAR_3; + case 0x15d1: // (blue) + element = EL_CHAR_EXCLAM; break; - case 0x166c: // (green steel) - element = EL_STEEL_CHAR_4; + case 0x15d2: // (blue) + element = EL_CHAR_COLON; break; - case 0x166d: // (green steel) - element = EL_STEEL_CHAR_5; + case 0x15d3: // (blue) + element = EL_CHAR_LESS; break; - case 0x166e: // (green steel) - element = EL_STEEL_CHAR_6; + case 0x15d4: // (blue) + element = EL_CHAR_GREATER; break; - case 0x166f: // (green steel) - element = EL_STEEL_CHAR_7; + case 0x15d5: // (blue) + element = EL_CHAR_QUESTION; break; - case 0x1670: // (green steel) - element = EL_STEEL_CHAR_8; + case 0x15d6: // (blue) + element = EL_CHAR_COPYRIGHT; break; - case 0x1671: // (green steel) - element = EL_STEEL_CHAR_9; + case 0x15d7: // (blue) + element = EL_CHAR_UP; break; - case 0x1672: // (green steel) - element = EL_STEEL_CHAR_PERIOD; + case 0x15d8: // (blue) + element = EL_CHAR_DOWN; break; - case 0x1673: // (green steel) - element = EL_STEEL_CHAR_EXCLAM; + case 0x15d9: // (blue) + element = EL_CHAR_BUTTON; break; - case 0x1674: // (green steel) - element = EL_STEEL_CHAR_COLON; + case 0x15da: // (blue) + element = EL_CHAR_PLUS; break; - case 0x1675: // (green steel) - element = EL_STEEL_CHAR_LESS; + case 0x15db: // (blue) + element = EL_CHAR_MINUS; break; - case 0x1676: // (green steel) - element = EL_STEEL_CHAR_GREATER; + case 0x15dc: // (blue) + element = EL_CHAR_APOSTROPHE; break; - case 0x1677: // (green steel) - element = EL_STEEL_CHAR_QUESTION; + case 0x15dd: // (blue) + element = EL_CHAR_PARENLEFT; break; - case 0x1678: // (green steel) - element = EL_STEEL_CHAR_COPYRIGHT; + case 0x15de: // (blue) + element = EL_CHAR_PARENRIGHT; break; - case 0x1679: // (green steel) - element = EL_STEEL_CHAR_UP; + case 0x15df: // (green) + element = EL_CHAR_A; break; - case 0x167a: // (green steel) - element = EL_STEEL_CHAR_DOWN; + case 0x15e0: // (green) + element = EL_CHAR_B; break; - case 0x167b: // (green steel) - element = EL_STEEL_CHAR_BUTTON; + case 0x15e1: // (green) + element = EL_CHAR_C; break; - case 0x167c: // (green steel) - element = EL_STEEL_CHAR_PLUS; + case 0x15e2: // (green) + element = EL_CHAR_D; break; - case 0x167d: // (green steel) - element = EL_STEEL_CHAR_MINUS; + case 0x15e3: // (green) + element = EL_CHAR_E; break; - case 0x167e: // (green steel) - element = EL_STEEL_CHAR_APOSTROPHE; + case 0x15e4: // (green) + element = EL_CHAR_F; break; - case 0x167f: // (green steel) - element = EL_STEEL_CHAR_PARENLEFT; + case 0x15e5: // (green) + element = EL_CHAR_G; break; - case 0x1680: // (green steel) - element = EL_STEEL_CHAR_PARENRIGHT; + case 0x15e6: // (green) + element = EL_CHAR_H; break; - case 0x1681: // gate (red) - element = EL_EM_GATE_1; + case 0x15e7: // (green) + element = EL_CHAR_I; break; - case 0x1682: // secret gate (red) - element = EL_EM_GATE_1_GRAY; + case 0x15e8: // (green) + element = EL_CHAR_J; break; - case 0x1683: // gate (yellow) - element = EL_EM_GATE_2; + case 0x15e9: // (green) + element = EL_CHAR_K; break; - case 0x1684: // secret gate (yellow) - element = EL_EM_GATE_2_GRAY; + case 0x15ea: // (green) + element = EL_CHAR_L; break; - case 0x1685: // gate (blue) - element = EL_EM_GATE_4; + case 0x15eb: // (green) + element = EL_CHAR_M; break; - case 0x1686: // secret gate (blue) - element = EL_EM_GATE_4_GRAY; + case 0x15ec: // (green) + element = EL_CHAR_N; break; - case 0x1687: // gate (green) - element = EL_EM_GATE_3; + case 0x15ed: // (green) + element = EL_CHAR_O; break; - case 0x1688: // secret gate (green) - element = EL_EM_GATE_3_GRAY; + case 0x15ee: // (green) + element = EL_CHAR_P; break; - case 0x1689: // gate (white) - element = EL_DC_GATE_WHITE; + case 0x15ef: // (green) + element = EL_CHAR_Q; break; - case 0x168a: // secret gate (white) - element = EL_DC_GATE_WHITE_GRAY; + case 0x15f0: // (green) + element = EL_CHAR_R; break; - case 0x168b: // secret gate (no key) - element = EL_DC_GATE_FAKE_GRAY; + case 0x15f1: // (green) + element = EL_CHAR_S; break; - case 0x168c: - element = EL_ROBOT_WHEEL; + case 0x15f2: // (green) + element = EL_CHAR_T; break; - case 0x168d: - element = EL_DC_TIMEGATE_SWITCH; + case 0x15f3: // (green) + element = EL_CHAR_U; break; - case 0x168e: - element = EL_ACID_POOL_BOTTOM; + case 0x15f4: // (green) + element = EL_CHAR_V; break; - case 0x168f: - element = EL_ACID_POOL_TOPLEFT; + case 0x15f5: // (green) + element = EL_CHAR_W; break; - case 0x1690: - element = EL_ACID_POOL_TOPRIGHT; + case 0x15f6: // (green) + element = EL_CHAR_X; break; - case 0x1691: - element = EL_ACID_POOL_BOTTOMLEFT; + case 0x15f7: // (green) + element = EL_CHAR_Y; break; - case 0x1692: - element = EL_ACID_POOL_BOTTOMRIGHT; + case 0x15f8: // (green) + element = EL_CHAR_Z; break; - case 0x1693: - element = EL_STEELWALL; + case 0x15f9: // (green) + element = EL_CHAR_AUMLAUT; break; - case 0x1694: - element = EL_STEELWALL_SLIPPERY; + case 0x15fa: // (green) + element = EL_CHAR_OUMLAUT; break; - case 0x1695: // steel wall (not round) - element = EL_STEELWALL; + case 0x15fb: // (green) + element = EL_CHAR_UUMLAUT; break; - case 0x1696: // steel wall (left) - element = EL_DC_STEELWALL_1_LEFT; + case 0x15fc: // (green) + element = EL_CHAR_0; break; - case 0x1697: // steel wall (bottom) - element = EL_DC_STEELWALL_1_BOTTOM; + case 0x15fd: // (green) + element = EL_CHAR_1; break; - case 0x1698: // steel wall (right) - element = EL_DC_STEELWALL_1_RIGHT; + case 0x15fe: // (green) + element = EL_CHAR_2; break; - case 0x1699: // steel wall (top) - element = EL_DC_STEELWALL_1_TOP; + case 0x15ff: // (green) + element = EL_CHAR_3; break; - case 0x169a: // steel wall (left/bottom) - element = EL_DC_STEELWALL_1_BOTTOMLEFT; + case 0x1600: // (green) + element = EL_CHAR_4; break; - case 0x169b: // steel wall (right/bottom) - element = EL_DC_STEELWALL_1_BOTTOMRIGHT; + case 0x1601: // (green) + element = EL_CHAR_5; break; - case 0x169c: // steel wall (right/top) - element = EL_DC_STEELWALL_1_TOPRIGHT; + case 0x1602: // (green) + element = EL_CHAR_6; break; - case 0x169d: // steel wall (left/top) - element = EL_DC_STEELWALL_1_TOPLEFT; + case 0x1603: // (green) + element = EL_CHAR_7; break; - case 0x169e: // steel wall (right/bottom small) - element = EL_DC_STEELWALL_1_BOTTOMRIGHT_2; + case 0x1604: // (green) + element = EL_CHAR_8; break; - case 0x169f: // steel wall (left/bottom small) - element = EL_DC_STEELWALL_1_BOTTOMLEFT_2; + case 0x1605: // (green) + element = EL_CHAR_9; break; - case 0x16a0: // steel wall (right/top small) - element = EL_DC_STEELWALL_1_TOPRIGHT_2; + case 0x1606: // (green) + element = EL_CHAR_PERIOD; break; - case 0x16a1: // steel wall (left/top small) - element = EL_DC_STEELWALL_1_TOPLEFT_2; + case 0x1607: // (green) + element = EL_CHAR_EXCLAM; break; - case 0x16a2: // steel wall (left/right) - element = EL_DC_STEELWALL_1_VERTICAL; + case 0x1608: // (green) + element = EL_CHAR_COLON; break; - case 0x16a3: // steel wall (top/bottom) - element = EL_DC_STEELWALL_1_HORIZONTAL; + case 0x1609: // (green) + element = EL_CHAR_LESS; break; - case 0x16a4: // steel wall 2 (left end) - element = EL_DC_STEELWALL_2_LEFT; + case 0x160a: // (green) + element = EL_CHAR_GREATER; break; - case 0x16a5: // steel wall 2 (right end) - element = EL_DC_STEELWALL_2_RIGHT; + case 0x160b: // (green) + element = EL_CHAR_QUESTION; break; - case 0x16a6: // steel wall 2 (top end) - element = EL_DC_STEELWALL_2_TOP; + case 0x160c: // (green) + element = EL_CHAR_COPYRIGHT; break; - case 0x16a7: // steel wall 2 (bottom end) - element = EL_DC_STEELWALL_2_BOTTOM; + case 0x160d: // (green) + element = EL_CHAR_UP; break; - case 0x16a8: // steel wall 2 (left/right) - element = EL_DC_STEELWALL_2_HORIZONTAL; + case 0x160e: // (green) + element = EL_CHAR_DOWN; break; - case 0x16a9: // steel wall 2 (up/down) - element = EL_DC_STEELWALL_2_VERTICAL; + case 0x160f: // (green) + element = EL_CHAR_BUTTON; break; - case 0x16aa: // steel wall 2 (mid) - element = EL_DC_STEELWALL_2_MIDDLE; + case 0x1610: // (green) + element = EL_CHAR_PLUS; break; - case 0x16ab: - element = EL_SIGN_EXCLAMATION; + case 0x1611: // (green) + element = EL_CHAR_MINUS; break; - case 0x16ac: - element = EL_SIGN_RADIOACTIVITY; + case 0x1612: // (green) + element = EL_CHAR_APOSTROPHE; break; - case 0x16ad: - element = EL_SIGN_STOP; + case 0x1613: // (green) + element = EL_CHAR_PARENLEFT; break; - case 0x16ae: - element = EL_SIGN_WHEELCHAIR; + case 0x1614: // (green) + element = EL_CHAR_PARENRIGHT; break; - case 0x16af: - element = EL_SIGN_PARKING; + case 0x1615: // (blue steel) + element = EL_STEEL_CHAR_A; break; - case 0x16b0: - element = EL_SIGN_NO_ENTRY; + case 0x1616: // (blue steel) + element = EL_STEEL_CHAR_B; break; - case 0x16b1: - element = EL_SIGN_HEART; + case 0x1617: // (blue steel) + element = EL_STEEL_CHAR_C; break; - case 0x16b2: - element = EL_SIGN_GIVE_WAY; + case 0x1618: // (blue steel) + element = EL_STEEL_CHAR_D; break; - case 0x16b3: - element = EL_SIGN_ENTRY_FORBIDDEN; + case 0x1619: // (blue steel) + element = EL_STEEL_CHAR_E; break; - case 0x16b4: - element = EL_SIGN_EMERGENCY_EXIT; + case 0x161a: // (blue steel) + element = EL_STEEL_CHAR_F; break; - case 0x16b5: - element = EL_SIGN_YIN_YANG; + case 0x161b: // (blue steel) + element = EL_STEEL_CHAR_G; break; - case 0x16b6: - element = EL_WALL_EMERALD; + case 0x161c: // (blue steel) + element = EL_STEEL_CHAR_H; break; - case 0x16b7: - element = EL_WALL_DIAMOND; + case 0x161d: // (blue steel) + element = EL_STEEL_CHAR_I; break; - case 0x16b8: - element = EL_WALL_PEARL; + case 0x161e: // (blue steel) + element = EL_STEEL_CHAR_J; break; - case 0x16b9: - element = EL_WALL_CRYSTAL; + case 0x161f: // (blue steel) + element = EL_STEEL_CHAR_K; break; - case 0x16ba: - element = EL_INVISIBLE_WALL; + case 0x1620: // (blue steel) + element = EL_STEEL_CHAR_L; break; - case 0x16bb: - element = EL_INVISIBLE_STEELWALL; + case 0x1621: // (blue steel) + element = EL_STEEL_CHAR_M; break; - // 0x16bc - 0x16cb: - // EL_INVISIBLE_SAND + case 0x1622: // (blue steel) + element = EL_STEEL_CHAR_N; + break; - case 0x16cc: - element = EL_LIGHT_SWITCH; + case 0x1623: // (blue steel) + element = EL_STEEL_CHAR_O; break; - case 0x16cd: - element = EL_ENVELOPE_1; + case 0x1624: // (blue steel) + element = EL_STEEL_CHAR_P; break; - default: - if (element >= 0x0117 && element <= 0x036e) // (?) - element = EL_DIAMOND; - else if (element >= 0x042d && element <= 0x0684) // (?) - element = EL_EMERALD; - else if (element >= 0x157c && element <= 0x158b) - element = EL_SAND; - else if (element >= 0x1590 && element <= 0x159f) - element = EL_DC_LANDMINE; - else if (element >= 0x16bc && element <= 0x16cb) - element = EL_INVISIBLE_SAND; - else - { - Warn("unknown Diamond Caves element 0x%04x", element); + case 0x1625: // (blue steel) + element = EL_STEEL_CHAR_Q; + break; - element = EL_UNKNOWN; - } + case 0x1626: // (blue steel) + element = EL_STEEL_CHAR_R; break; - } - return getMappedElement(element); -} + case 0x1627: // (blue steel) + element = EL_STEEL_CHAR_S; + break; -static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, - int nr) -{ - byte header[DC_LEVEL_HEADER_SIZE]; - int envelope_size; - int envelope_header_pos = 62; - int envelope_content_pos = 94; - int level_name_pos = 251; - int level_author_pos = 292; - int envelope_header_len; - int envelope_content_len; - int level_name_len; - int level_author_len; - int fieldx, fieldy; - int num_yamyam_contents; - int i, x, y; + case 0x1628: // (blue steel) + element = EL_STEEL_CHAR_T; + break; - getDecodedWord_DC(0, TRUE); // initialize DC2 decoding engine + case 0x1629: // (blue steel) + element = EL_STEEL_CHAR_U; + break; - for (i = 0; i < DC_LEVEL_HEADER_SIZE / 2; i++) - { - unsigned short header_word = getDecodedWord_DC(getFile16BitBE(file), FALSE); + case 0x162a: // (blue steel) + element = EL_STEEL_CHAR_V; + break; - header[i * 2 + 0] = header_word >> 8; - header[i * 2 + 1] = header_word & 0xff; - } + case 0x162b: // (blue steel) + element = EL_STEEL_CHAR_W; + break; - // read some values from level header to check level decoding integrity - fieldx = header[6] | (header[7] << 8); - fieldy = header[8] | (header[9] << 8); - num_yamyam_contents = header[60] | (header[61] << 8); + case 0x162c: // (blue steel) + element = EL_STEEL_CHAR_X; + break; - // do some simple sanity checks to ensure that level was correctly decoded - if (fieldx < 1 || fieldx > 256 || - fieldy < 1 || fieldy > 256 || - num_yamyam_contents < 1 || num_yamyam_contents > 8) - { - level->no_valid_file = TRUE; + case 0x162d: // (blue steel) + element = EL_STEEL_CHAR_Y; + break; - Warn("cannot decode level from stream -- using empty level"); + case 0x162e: // (blue steel) + element = EL_STEEL_CHAR_Z; + break; - return; - } + case 0x162f: // (blue steel) + element = EL_STEEL_CHAR_AUMLAUT; + break; - // maximum envelope header size is 31 bytes - envelope_header_len = header[envelope_header_pos]; - // maximum envelope content size is 110 (156?) bytes - envelope_content_len = header[envelope_content_pos]; + case 0x1630: // (blue steel) + element = EL_STEEL_CHAR_OUMLAUT; + break; - // maximum level title size is 40 bytes - level_name_len = MIN(header[level_name_pos], MAX_LEVEL_NAME_LEN); - // maximum level author size is 30 (51?) bytes - level_author_len = MIN(header[level_author_pos], MAX_LEVEL_AUTHOR_LEN); + case 0x1631: // (blue steel) + element = EL_STEEL_CHAR_UUMLAUT; + break; - envelope_size = 0; + case 0x1632: // (blue steel) + element = EL_STEEL_CHAR_0; + break; - for (i = 0; i < envelope_header_len; i++) - if (envelope_size < MAX_ENVELOPE_TEXT_LEN) - level->envelope[0].text[envelope_size++] = - header[envelope_header_pos + 1 + i]; + case 0x1633: // (blue steel) + element = EL_STEEL_CHAR_1; + break; - if (envelope_header_len > 0 && envelope_content_len > 0) - { - if (envelope_size < MAX_ENVELOPE_TEXT_LEN) - level->envelope[0].text[envelope_size++] = '\n'; - if (envelope_size < MAX_ENVELOPE_TEXT_LEN) - level->envelope[0].text[envelope_size++] = '\n'; - } + case 0x1634: // (blue steel) + element = EL_STEEL_CHAR_2; + break; - for (i = 0; i < envelope_content_len; i++) - if (envelope_size < MAX_ENVELOPE_TEXT_LEN) - level->envelope[0].text[envelope_size++] = - header[envelope_content_pos + 1 + i]; + case 0x1635: // (blue steel) + element = EL_STEEL_CHAR_3; + break; - level->envelope[0].text[envelope_size] = '\0'; + case 0x1636: // (blue steel) + element = EL_STEEL_CHAR_4; + break; - level->envelope[0].xsize = MAX_ENVELOPE_XSIZE; - level->envelope[0].ysize = 10; - level->envelope[0].autowrap = TRUE; - level->envelope[0].centered = TRUE; + case 0x1637: // (blue steel) + element = EL_STEEL_CHAR_5; + break; - for (i = 0; i < level_name_len; i++) - level->name[i] = header[level_name_pos + 1 + i]; - level->name[level_name_len] = '\0'; + case 0x1638: // (blue steel) + element = EL_STEEL_CHAR_6; + break; - for (i = 0; i < level_author_len; i++) - level->author[i] = header[level_author_pos + 1 + i]; - level->author[level_author_len] = '\0'; + case 0x1639: // (blue steel) + element = EL_STEEL_CHAR_7; + break; - num_yamyam_contents = header[60] | (header[61] << 8); - level->num_yamyam_contents = - MIN(MAX(MIN_ELEMENT_CONTENTS, num_yamyam_contents), MAX_ELEMENT_CONTENTS); + case 0x163a: // (blue steel) + element = EL_STEEL_CHAR_8; + break; - for (i = 0; i < num_yamyam_contents; i++) - { - for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - { - unsigned short word = getDecodedWord_DC(getFile16BitBE(file), FALSE); - int element_dc = ((word & 0xff) << 8) | ((word >> 8) & 0xff); + case 0x163b: // (blue steel) + element = EL_STEEL_CHAR_9; + break; - if (i < MAX_ELEMENT_CONTENTS) - level->yamyam_content[i].e[x][y] = getMappedElement_DC(element_dc); - } - } + case 0x163c: // (blue steel) + element = EL_STEEL_CHAR_PERIOD; + break; - fieldx = header[6] | (header[7] << 8); - fieldy = header[8] | (header[9] << 8); - level->fieldx = MIN(MAX(MIN_LEV_FIELDX, fieldx), MAX_LEV_FIELDX); - level->fieldy = MIN(MAX(MIN_LEV_FIELDY, fieldy), MAX_LEV_FIELDY); + case 0x163d: // (blue steel) + element = EL_STEEL_CHAR_EXCLAM; + break; - for (y = 0; y < fieldy; y++) for (x = 0; x < fieldx; x++) - { - unsigned short word = getDecodedWord_DC(getFile16BitBE(file), FALSE); - int element_dc = ((word & 0xff) << 8) | ((word >> 8) & 0xff); + case 0x163e: // (blue steel) + element = EL_STEEL_CHAR_COLON; + break; - if (x < MAX_LEV_FIELDX && y < MAX_LEV_FIELDY) - level->field[x][y] = getMappedElement_DC(element_dc); - } + case 0x163f: // (blue steel) + element = EL_STEEL_CHAR_LESS; + break; - x = MIN(MAX(0, (header[10] | (header[11] << 8)) - 1), MAX_LEV_FIELDX - 1); - y = MIN(MAX(0, (header[12] | (header[13] << 8)) - 1), MAX_LEV_FIELDY - 1); - level->field[x][y] = EL_PLAYER_1; + case 0x1640: // (blue steel) + element = EL_STEEL_CHAR_GREATER; + break; - x = MIN(MAX(0, (header[14] | (header[15] << 8)) - 1), MAX_LEV_FIELDX - 1); - y = MIN(MAX(0, (header[16] | (header[17] << 8)) - 1), MAX_LEV_FIELDY - 1); - level->field[x][y] = EL_PLAYER_2; + case 0x1641: // (blue steel) + element = EL_STEEL_CHAR_QUESTION; + break; - level->gems_needed = header[18] | (header[19] << 8); + case 0x1642: // (blue steel) + element = EL_STEEL_CHAR_COPYRIGHT; + break; - level->score[SC_EMERALD] = header[20] | (header[21] << 8); - level->score[SC_DIAMOND] = header[22] | (header[23] << 8); - level->score[SC_PEARL] = header[24] | (header[25] << 8); - level->score[SC_CRYSTAL] = header[26] | (header[27] << 8); - level->score[SC_NUT] = header[28] | (header[29] << 8); - level->score[SC_ROBOT] = header[30] | (header[31] << 8); - level->score[SC_SPACESHIP] = header[32] | (header[33] << 8); - level->score[SC_BUG] = header[34] | (header[35] << 8); - level->score[SC_YAMYAM] = header[36] | (header[37] << 8); - level->score[SC_DYNAMITE] = header[38] | (header[39] << 8); - level->score[SC_KEY] = header[40] | (header[41] << 8); - level->score[SC_TIME_BONUS] = header[42] | (header[43] << 8); + case 0x1643: // (blue steel) + element = EL_STEEL_CHAR_UP; + break; - level->time = header[44] | (header[45] << 8); + case 0x1644: // (blue steel) + element = EL_STEEL_CHAR_DOWN; + break; - level->amoeba_speed = header[46] | (header[47] << 8); - level->time_light = header[48] | (header[49] << 8); - level->time_timegate = header[50] | (header[51] << 8); - level->time_wheel = header[52] | (header[53] << 8); - level->time_magic_wall = header[54] | (header[55] << 8); - level->extra_time = header[56] | (header[57] << 8); - level->shield_normal_time = header[58] | (header[59] << 8); + case 0x1645: // (blue steel) + element = EL_STEEL_CHAR_BUTTON; + break; - // shield and extra time elements do not have a score - level->score[SC_SHIELD] = 0; - level->extra_time_score = 0; + case 0x1646: // (blue steel) + element = EL_STEEL_CHAR_PLUS; + break; - // set time for normal and deadly shields to the same value - level->shield_deadly_time = level->shield_normal_time; + case 0x1647: // (blue steel) + element = EL_STEEL_CHAR_MINUS; + break; - // Diamond Caves has the same (strange) behaviour as Emerald Mine that gems - // can slip down from flat walls, like normal walls and steel walls - level->em_slippery_gems = TRUE; + case 0x1648: // (blue steel) + element = EL_STEEL_CHAR_APOSTROPHE; + break; - // time score is counted for each 10 seconds left in Diamond Caves levels - level->time_score_base = 10; -} + case 0x1649: // (blue steel) + element = EL_STEEL_CHAR_PARENLEFT; + break; -static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - char *filename = level_file_info->filename; - File *file; - int num_magic_bytes = 8; - char magic_bytes[num_magic_bytes + 1]; - int num_levels_to_skip = level_file_info->nr - leveldir_current->first_level; + case 0x164a: // (blue steel) + element = EL_STEEL_CHAR_PARENRIGHT; + break; - if (!(file = openFile(filename, MODE_READ))) - { - level->no_valid_file = TRUE; + case 0x164b: // (green steel) + element = EL_STEEL_CHAR_A; + break; - if (!level_info_only) - Warn("cannot read level '%s' -- using empty level", filename); + case 0x164c: // (green steel) + element = EL_STEEL_CHAR_B; + break; - return; - } + case 0x164d: // (green steel) + element = EL_STEEL_CHAR_C; + break; - // fseek(file, 0x0000, SEEK_SET); + case 0x164e: // (green steel) + element = EL_STEEL_CHAR_D; + break; - if (level_file_info->packed) - { - // read "magic bytes" from start of file - if (getStringFromFile(file, magic_bytes, num_magic_bytes + 1) == NULL) - magic_bytes[0] = '\0'; + case 0x164f: // (green steel) + element = EL_STEEL_CHAR_E; + break; - // check "magic bytes" for correct file format - if (!strPrefix(magic_bytes, "DC2")) - { - level->no_valid_file = TRUE; + case 0x1650: // (green steel) + element = EL_STEEL_CHAR_F; + break; - Warn("unknown DC level file '%s' -- using empty level", filename); + case 0x1651: // (green steel) + element = EL_STEEL_CHAR_G; + break; - return; - } + case 0x1652: // (green steel) + element = EL_STEEL_CHAR_H; + break; - if (strPrefix(magic_bytes, "DC2Win95") || - strPrefix(magic_bytes, "DC2Win98")) - { - int position_first_level = 0x00fa; - int extra_bytes = 4; - int skip_bytes; + case 0x1653: // (green steel) + element = EL_STEEL_CHAR_I; + break; - // advance file stream to first level inside the level package - skip_bytes = position_first_level - num_magic_bytes - extra_bytes; + case 0x1654: // (green steel) + element = EL_STEEL_CHAR_J; + break; - // each block of level data is followed by block of non-level data - num_levels_to_skip *= 2; + case 0x1655: // (green steel) + element = EL_STEEL_CHAR_K; + break; - // at least skip header bytes, therefore use ">= 0" instead of "> 0" - while (num_levels_to_skip >= 0) - { - // advance file stream to next level inside the level package - if (seekFile(file, skip_bytes, SEEK_CUR) != 0) - { - level->no_valid_file = TRUE; + case 0x1656: // (green steel) + element = EL_STEEL_CHAR_L; + break; - Warn("cannot fseek in file '%s' -- using empty level", filename); + case 0x1657: // (green steel) + element = EL_STEEL_CHAR_M; + break; - return; - } + case 0x1658: // (green steel) + element = EL_STEEL_CHAR_N; + break; - // skip apparently unused extra bytes following each level - ReadUnusedBytesFromFile(file, extra_bytes); + case 0x1659: // (green steel) + element = EL_STEEL_CHAR_O; + break; - // read size of next level in level package - skip_bytes = getFile32BitLE(file); + case 0x165a: // (green steel) + element = EL_STEEL_CHAR_P; + break; - num_levels_to_skip--; - } - } - else - { - level->no_valid_file = TRUE; + case 0x165b: // (green steel) + element = EL_STEEL_CHAR_Q; + break; - Warn("unknown DC2 level file '%s' -- using empty level", filename); + case 0x165c: // (green steel) + element = EL_STEEL_CHAR_R; + break; - return; - } - } + case 0x165d: // (green steel) + element = EL_STEEL_CHAR_S; + break; - LoadLevelFromFileStream_DC(file, level, level_file_info->nr); + case 0x165e: // (green steel) + element = EL_STEEL_CHAR_T; + break; - closeFile(file); -} + case 0x165f: // (green steel) + element = EL_STEEL_CHAR_U; + break; + case 0x1660: // (green steel) + element = EL_STEEL_CHAR_V; + break; -// ---------------------------------------------------------------------------- -// functions for loading SB level -// ---------------------------------------------------------------------------- + case 0x1661: // (green steel) + element = EL_STEEL_CHAR_W; + break; -int getMappedElement_SB(int element_ascii, boolean use_ces) -{ - static struct - { - int ascii; - int sb; - int ce; - } - sb_element_mapping[] = - { - { ' ', EL_EMPTY, EL_CUSTOM_1 }, // floor (space) - { '#', EL_STEELWALL, EL_CUSTOM_2 }, // wall - { '@', EL_PLAYER_1, EL_CUSTOM_3 }, // player - { '$', EL_SOKOBAN_OBJECT, EL_CUSTOM_4 }, // box - { '.', EL_SOKOBAN_FIELD_EMPTY, EL_CUSTOM_5 }, // goal square - { '*', EL_SOKOBAN_FIELD_FULL, EL_CUSTOM_6 }, // box on goal square - { '+', EL_SOKOBAN_FIELD_PLAYER, EL_CUSTOM_7 }, // player on goal square - { '_', EL_INVISIBLE_STEELWALL, EL_FROM_LEVEL_TEMPLATE }, // floor beyond border + case 0x1662: // (green steel) + element = EL_STEEL_CHAR_X; + break; - { 0, -1, -1 }, - }; + case 0x1663: // (green steel) + element = EL_STEEL_CHAR_Y; + break; - int i; + case 0x1664: // (green steel) + element = EL_STEEL_CHAR_Z; + break; - for (i = 0; sb_element_mapping[i].ascii != 0; i++) - if (element_ascii == sb_element_mapping[i].ascii) - return (use_ces ? sb_element_mapping[i].ce : sb_element_mapping[i].sb); + case 0x1665: // (green steel) + element = EL_STEEL_CHAR_AUMLAUT; + break; - return EL_UNDEFINED; -} + case 0x1666: // (green steel) + element = EL_STEEL_CHAR_OUMLAUT; + break; -static void SetLevelSettings_SB(struct LevelInfo *level) -{ - // time settings - level->time = 0; - level->use_step_counter = TRUE; + case 0x1667: // (green steel) + element = EL_STEEL_CHAR_UUMLAUT; + break; - // score settings - level->score[SC_TIME_BONUS] = 0; - level->time_score_base = 1; - level->rate_time_over_score = TRUE; + case 0x1668: // (green steel) + element = EL_STEEL_CHAR_0; + break; - // game settings - level->auto_exit_sokoban = TRUE; -} + case 0x1669: // (green steel) + element = EL_STEEL_CHAR_1; + break; -static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - char *filename = level_file_info->filename; - char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN]; - char last_comment[MAX_LINE_LEN]; - char level_name[MAX_LINE_LEN]; - char *line_ptr; - File *file; - int num_levels_to_skip = level_file_info->nr - leveldir_current->first_level; - boolean read_continued_line = FALSE; - boolean reading_playfield = FALSE; - boolean got_valid_playfield_line = FALSE; - boolean invalid_playfield_char = FALSE; - boolean load_xsb_to_ces = check_special_flags("load_xsb_to_ces"); - int file_level_nr = 0; - int line_nr = 0; - int x = 0, y = 0; // initialized to make compilers happy + case 0x166a: // (green steel) + element = EL_STEEL_CHAR_2; + break; - last_comment[0] = '\0'; - level_name[0] = '\0'; + case 0x166b: // (green steel) + element = EL_STEEL_CHAR_3; + break; - if (!(file = openFile(filename, MODE_READ))) - { - level->no_valid_file = TRUE; + case 0x166c: // (green steel) + element = EL_STEEL_CHAR_4; + break; - if (!level_info_only) - Warn("cannot read level '%s' -- using empty level", filename); + case 0x166d: // (green steel) + element = EL_STEEL_CHAR_5; + break; - return; - } + case 0x166e: // (green steel) + element = EL_STEEL_CHAR_6; + break; - while (!checkEndOfFile(file)) - { - // level successfully read, but next level may follow here - if (!got_valid_playfield_line && reading_playfield) - { - // read playfield from single level file -- skip remaining file - if (!level_file_info->packed) - break; + case 0x166f: // (green steel) + element = EL_STEEL_CHAR_7; + break; - if (file_level_nr >= num_levels_to_skip) - break; + case 0x1670: // (green steel) + element = EL_STEEL_CHAR_8; + break; - file_level_nr++; + case 0x1671: // (green steel) + element = EL_STEEL_CHAR_9; + break; - last_comment[0] = '\0'; - level_name[0] = '\0'; + case 0x1672: // (green steel) + element = EL_STEEL_CHAR_PERIOD; + break; - reading_playfield = FALSE; - } + case 0x1673: // (green steel) + element = EL_STEEL_CHAR_EXCLAM; + break; - got_valid_playfield_line = FALSE; + case 0x1674: // (green steel) + element = EL_STEEL_CHAR_COLON; + break; - // read next line of input file - if (!getStringFromFile(file, line, MAX_LINE_LEN)) + case 0x1675: // (green steel) + element = EL_STEEL_CHAR_LESS; break; - // check if line was completely read and is terminated by line break - if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') - line_nr++; + case 0x1676: // (green steel) + element = EL_STEEL_CHAR_GREATER; + break; - // cut trailing line break (this can be newline and/or carriage return) - for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--) - if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0') - *line_ptr = '\0'; + case 0x1677: // (green steel) + element = EL_STEEL_CHAR_QUESTION; + break; - // copy raw input line for later use (mainly debugging output) - strcpy(line_raw, line); + case 0x1678: // (green steel) + element = EL_STEEL_CHAR_COPYRIGHT; + break; - if (read_continued_line) - { - // append new line to existing line, if there is enough space - if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN) - strcat(previous_line, line_ptr); + case 0x1679: // (green steel) + element = EL_STEEL_CHAR_UP; + break; - strcpy(line, previous_line); // copy storage buffer to line + case 0x167a: // (green steel) + element = EL_STEEL_CHAR_DOWN; + break; - read_continued_line = FALSE; - } + case 0x167b: // (green steel) + element = EL_STEEL_CHAR_BUTTON; + break; - // if the last character is '\', continue at next line - if (strlen(line) > 0 && line[strlen(line) - 1] == '\\') - { - line[strlen(line) - 1] = '\0'; // cut off trailing backslash - strcpy(previous_line, line); // copy line to storage buffer + case 0x167c: // (green steel) + element = EL_STEEL_CHAR_PLUS; + break; - read_continued_line = TRUE; + case 0x167d: // (green steel) + element = EL_STEEL_CHAR_MINUS; + break; - continue; - } + case 0x167e: // (green steel) + element = EL_STEEL_CHAR_APOSTROPHE; + break; - // skip empty lines - if (line[0] == '\0') - continue; + case 0x167f: // (green steel) + element = EL_STEEL_CHAR_PARENLEFT; + break; - // extract comment text from comment line - if (line[0] == ';') - { - for (line_ptr = line; *line_ptr; line_ptr++) - if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != ';') - break; + case 0x1680: // (green steel) + element = EL_STEEL_CHAR_PARENRIGHT; + break; - strcpy(last_comment, line_ptr); + case 0x1681: // gate (red) + element = EL_EM_GATE_1; + break; - continue; - } + case 0x1682: // secret gate (red) + element = EL_EM_GATE_1_GRAY; + break; - // extract level title text from line containing level title - if (line[0] == '\'') - { - strcpy(level_name, &line[1]); + case 0x1683: // gate (yellow) + element = EL_EM_GATE_2; + break; - if (strlen(level_name) > 0 && level_name[strlen(level_name) - 1] == '\'') - level_name[strlen(level_name) - 1] = '\0'; + case 0x1684: // secret gate (yellow) + element = EL_EM_GATE_2_GRAY; + break; - continue; - } + case 0x1685: // gate (blue) + element = EL_EM_GATE_4; + break; - // skip lines containing only spaces (or empty lines) - for (line_ptr = line; *line_ptr; line_ptr++) - if (*line_ptr != ' ') - break; - if (*line_ptr == '\0') - continue; + case 0x1686: // secret gate (blue) + element = EL_EM_GATE_4_GRAY; + break; - // at this point, we have found a line containing part of a playfield + case 0x1687: // gate (green) + element = EL_EM_GATE_3; + break; - got_valid_playfield_line = TRUE; + case 0x1688: // secret gate (green) + element = EL_EM_GATE_3_GRAY; + break; - if (!reading_playfield) - { - reading_playfield = TRUE; - invalid_playfield_char = FALSE; + case 0x1689: // gate (white) + element = EL_DC_GATE_WHITE; + break; - for (x = 0; x < MAX_LEV_FIELDX; x++) - for (y = 0; y < MAX_LEV_FIELDY; y++) - level->field[x][y] = getMappedElement_SB(' ', load_xsb_to_ces); + case 0x168a: // secret gate (white) + element = EL_DC_GATE_WHITE_GRAY; + break; - level->fieldx = 0; - level->fieldy = 0; + case 0x168b: // secret gate (no key) + element = EL_DC_GATE_FAKE_GRAY; + break; - // start with topmost tile row - y = 0; - } + case 0x168c: + element = EL_ROBOT_WHEEL; + break; - // skip playfield line if larger row than allowed - if (y >= MAX_LEV_FIELDY) - continue; + case 0x168d: + element = EL_DC_TIMEGATE_SWITCH; + break; - // start with leftmost tile column - x = 0; + case 0x168e: + element = EL_ACID_POOL_BOTTOM; + break; - // read playfield elements from line - for (line_ptr = line; *line_ptr; line_ptr++) - { - int mapped_sb_element = getMappedElement_SB(*line_ptr, load_xsb_to_ces); + case 0x168f: + element = EL_ACID_POOL_TOPLEFT; + break; - // stop parsing playfield line if larger column than allowed - if (x >= MAX_LEV_FIELDX) - break; + case 0x1690: + element = EL_ACID_POOL_TOPRIGHT; + break; - if (mapped_sb_element == EL_UNDEFINED) - { - invalid_playfield_char = TRUE; + case 0x1691: + element = EL_ACID_POOL_BOTTOMLEFT; + break; - break; - } + case 0x1692: + element = EL_ACID_POOL_BOTTOMRIGHT; + break; - level->field[x][y] = mapped_sb_element; + case 0x1693: + element = EL_STEELWALL; + break; - // continue with next tile column - x++; + case 0x1694: + element = EL_STEELWALL_SLIPPERY; + break; - level->fieldx = MAX(x, level->fieldx); - } + case 0x1695: // steel wall (not round) + element = EL_STEELWALL; + break; - if (invalid_playfield_char) - { - // if first playfield line, treat invalid lines as comment lines - if (y == 0) - reading_playfield = FALSE; + case 0x1696: // steel wall (left) + element = EL_DC_STEELWALL_1_LEFT; + break; - continue; - } + case 0x1697: // steel wall (bottom) + element = EL_DC_STEELWALL_1_BOTTOM; + break; - // continue with next tile row - y++; - } + case 0x1698: // steel wall (right) + element = EL_DC_STEELWALL_1_RIGHT; + break; - closeFile(file); + case 0x1699: // steel wall (top) + element = EL_DC_STEELWALL_1_TOP; + break; - level->fieldy = y; + case 0x169a: // steel wall (left/bottom) + element = EL_DC_STEELWALL_1_BOTTOMLEFT; + break; - level->fieldx = MIN(MAX(MIN_LEV_FIELDX, level->fieldx), MAX_LEV_FIELDX); - level->fieldy = MIN(MAX(MIN_LEV_FIELDY, level->fieldy), MAX_LEV_FIELDY); + case 0x169b: // steel wall (right/bottom) + element = EL_DC_STEELWALL_1_BOTTOMRIGHT; + break; - if (!reading_playfield) - { - level->no_valid_file = TRUE; + case 0x169c: // steel wall (right/top) + element = EL_DC_STEELWALL_1_TOPRIGHT; + break; - Warn("cannot read level '%s' -- using empty level", filename); + case 0x169d: // steel wall (left/top) + element = EL_DC_STEELWALL_1_TOPLEFT; + break; - return; - } + case 0x169e: // steel wall (right/bottom small) + element = EL_DC_STEELWALL_1_BOTTOMRIGHT_2; + break; - if (*level_name != '\0') - { - strncpy(level->name, level_name, MAX_LEVEL_NAME_LEN); - level->name[MAX_LEVEL_NAME_LEN] = '\0'; - } - else if (*last_comment != '\0') - { - strncpy(level->name, last_comment, MAX_LEVEL_NAME_LEN); - level->name[MAX_LEVEL_NAME_LEN] = '\0'; - } - else - { - sprintf(level->name, "--> Level %d <--", level_file_info->nr); - } + case 0x169f: // steel wall (left/bottom small) + element = EL_DC_STEELWALL_1_BOTTOMLEFT_2; + break; - // set all empty fields beyond the border walls to invisible steel wall - for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++) - { - if ((x == 0 || x == level->fieldx - 1 || - y == 0 || y == level->fieldy - 1) && - level->field[x][y] == getMappedElement_SB(' ', load_xsb_to_ces)) - FloodFillLevel(x, y, getMappedElement_SB('_', load_xsb_to_ces), - level->field, level->fieldx, level->fieldy); - } + case 0x16a0: // steel wall (right/top small) + element = EL_DC_STEELWALL_1_TOPRIGHT_2; + break; - // set special level settings for Sokoban levels - SetLevelSettings_SB(level); + case 0x16a1: // steel wall (left/top small) + element = EL_DC_STEELWALL_1_TOPLEFT_2; + break; - if (load_xsb_to_ces) - { - // special global settings can now be set in level template - level->use_custom_template = TRUE; - } -} + case 0x16a2: // steel wall (left/right) + element = EL_DC_STEELWALL_1_VERTICAL; + break; + case 0x16a3: // steel wall (top/bottom) + element = EL_DC_STEELWALL_1_HORIZONTAL; + break; -// ------------------------------------------------------------------------- -// functions for handling native levels -// ------------------------------------------------------------------------- + case 0x16a4: // steel wall 2 (left end) + element = EL_DC_STEELWALL_2_LEFT; + break; -static void LoadLevelFromFileInfo_EM(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - if (!LoadNativeLevel_EM(level_file_info->filename, level_info_only)) - level->no_valid_file = TRUE; -} + case 0x16a5: // steel wall 2 (right end) + element = EL_DC_STEELWALL_2_RIGHT; + break; -static void LoadLevelFromFileInfo_SP(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - int pos = 0; + case 0x16a6: // steel wall 2 (top end) + element = EL_DC_STEELWALL_2_TOP; + break; - // determine position of requested level inside level package - if (level_file_info->packed) - pos = level_file_info->nr - leveldir_current->first_level; + case 0x16a7: // steel wall 2 (bottom end) + element = EL_DC_STEELWALL_2_BOTTOM; + break; - if (!LoadNativeLevel_SP(level_file_info->filename, pos, level_info_only)) - level->no_valid_file = TRUE; -} + case 0x16a8: // steel wall 2 (left/right) + element = EL_DC_STEELWALL_2_HORIZONTAL; + break; -static void LoadLevelFromFileInfo_MM(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - if (!LoadNativeLevel_MM(level_file_info->filename, level_info_only)) - level->no_valid_file = TRUE; -} + case 0x16a9: // steel wall 2 (up/down) + element = EL_DC_STEELWALL_2_VERTICAL; + break; -void CopyNativeLevel_RND_to_Native(struct LevelInfo *level) -{ - if (level->game_engine_type == GAME_ENGINE_TYPE_EM) - CopyNativeLevel_RND_to_EM(level); - else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) - CopyNativeLevel_RND_to_SP(level); - else if (level->game_engine_type == GAME_ENGINE_TYPE_MM) - CopyNativeLevel_RND_to_MM(level); -} + case 0x16aa: // steel wall 2 (mid) + element = EL_DC_STEELWALL_2_MIDDLE; + break; -void CopyNativeLevel_Native_to_RND(struct LevelInfo *level) -{ - if (level->game_engine_type == GAME_ENGINE_TYPE_EM) - CopyNativeLevel_EM_to_RND(level); - else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) - CopyNativeLevel_SP_to_RND(level); - else if (level->game_engine_type == GAME_ENGINE_TYPE_MM) - CopyNativeLevel_MM_to_RND(level); -} + case 0x16ab: + element = EL_SIGN_EXCLAMATION; + break; -void SaveNativeLevel(struct LevelInfo *level) -{ - if (level->game_engine_type == GAME_ENGINE_TYPE_SP) - { - char *basename = getSingleLevelBasenameExt(level->file_info.nr, "sp"); - char *filename = getLevelFilenameFromBasename(basename); + case 0x16ac: + element = EL_SIGN_RADIOACTIVITY; + break; - CopyNativeLevel_RND_to_SP(level); - CopyNativeTape_RND_to_SP(level); + case 0x16ad: + element = EL_SIGN_STOP; + break; - SaveNativeLevel_SP(filename); - } -} + case 0x16ae: + element = EL_SIGN_WHEELCHAIR; + break; + case 0x16af: + element = EL_SIGN_PARKING; + break; -// ---------------------------------------------------------------------------- -// functions for loading generic level -// ---------------------------------------------------------------------------- + case 0x16b0: + element = EL_SIGN_NO_ENTRY; + break; -static void LoadLevelFromFileInfo(struct LevelInfo *level, - struct LevelFileInfo *level_file_info, - boolean level_info_only) -{ - // always start with reliable default values - setLevelInfoToDefaults(level, level_info_only, TRUE); + case 0x16b1: + element = EL_SIGN_HEART; + break; - switch (level_file_info->type) - { - case LEVEL_FILE_TYPE_RND: - LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only); + case 0x16b2: + element = EL_SIGN_GIVE_WAY; break; - case LEVEL_FILE_TYPE_EM: - LoadLevelFromFileInfo_EM(level, level_file_info, level_info_only); - level->game_engine_type = GAME_ENGINE_TYPE_EM; + case 0x16b3: + element = EL_SIGN_ENTRY_FORBIDDEN; break; - case LEVEL_FILE_TYPE_SP: - LoadLevelFromFileInfo_SP(level, level_file_info, level_info_only); - level->game_engine_type = GAME_ENGINE_TYPE_SP; + case 0x16b4: + element = EL_SIGN_EMERGENCY_EXIT; break; - case LEVEL_FILE_TYPE_MM: - LoadLevelFromFileInfo_MM(level, level_file_info, level_info_only); - level->game_engine_type = GAME_ENGINE_TYPE_MM; + case 0x16b5: + element = EL_SIGN_YIN_YANG; break; - case LEVEL_FILE_TYPE_DC: - LoadLevelFromFileInfo_DC(level, level_file_info, level_info_only); + case 0x16b6: + element = EL_WALL_EMERALD; break; - case LEVEL_FILE_TYPE_SB: - LoadLevelFromFileInfo_SB(level, level_file_info, level_info_only); + case 0x16b7: + element = EL_WALL_DIAMOND; break; - default: - LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only); + case 0x16b8: + element = EL_WALL_PEARL; break; - } - // if level file is invalid, restore level structure to default values - if (level->no_valid_file) - setLevelInfoToDefaults(level, level_info_only, FALSE); + case 0x16b9: + element = EL_WALL_CRYSTAL; + break; - if (level->game_engine_type == GAME_ENGINE_TYPE_UNKNOWN) - level->game_engine_type = GAME_ENGINE_TYPE_RND; + case 0x16ba: + element = EL_INVISIBLE_WALL; + break; - if (level_file_info->type != LEVEL_FILE_TYPE_RND) - CopyNativeLevel_Native_to_RND(level); -} + case 0x16bb: + element = EL_INVISIBLE_STEELWALL; + break; -void LoadLevelFromFilename(struct LevelInfo *level, char *filename) -{ - static struct LevelFileInfo level_file_info; + // 0x16bc - 0x16cb: + // EL_INVISIBLE_SAND - // always start with reliable default values - setFileInfoToDefaults(&level_file_info); + case 0x16cc: + element = EL_LIGHT_SWITCH; + break; - level_file_info.nr = 0; // unknown level number - level_file_info.type = LEVEL_FILE_TYPE_RND; // no others supported yet + case 0x16cd: + element = EL_ENVELOPE_1; + break; - setString(&level_file_info.filename, filename); + default: + if (element >= 0x0117 && element <= 0x036e) // (?) + element = EL_DIAMOND; + else if (element >= 0x042d && element <= 0x0684) // (?) + element = EL_EMERALD; + else if (element >= 0x157c && element <= 0x158b) + element = EL_SAND; + else if (element >= 0x1590 && element <= 0x159f) + element = EL_DC_LANDMINE; + else if (element >= 0x16bc && element <= 0x16cb) + element = EL_INVISIBLE_SAND; + else + { + Warn("unknown Diamond Caves element 0x%04x", element); - LoadLevelFromFileInfo(level, &level_file_info, FALSE); + element = EL_UNKNOWN; + } + break; + } + + return getMappedElement(element); } -static void LoadLevel_InitVersion(struct LevelInfo *level) +static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level) { - int i, j; - - if (leveldir_current == NULL) // only when dumping level - return; + byte header[DC_LEVEL_HEADER_SIZE]; + int envelope_size; + int envelope_header_pos = 62; + int envelope_content_pos = 94; + int level_name_pos = 251; + int level_author_pos = 292; + int envelope_header_len; + int envelope_content_len; + int level_name_len; + int level_author_len; + int fieldx, fieldy; + int num_yamyam_contents; + int i, x, y; - // all engine modifications also valid for levels which use latest engine - if (level->game_version < VERSION_IDENT(3,2,0,5)) - { - // time bonus score was given for 10 s instead of 1 s before 3.2.0-5 - level->time_score_base = 10; - } + getDecodedWord_DC(0, TRUE); // initialize DC2 decoding engine - if (leveldir_current->latest_engine) + for (i = 0; i < DC_LEVEL_HEADER_SIZE / 2; i++) { - // ---------- use latest game engine -------------------------------------- + unsigned short header_word = getDecodedWord_DC(getFile16BitBE(file), FALSE); - /* For all levels which are forced to use the latest game engine version - (normally all but user contributed, private and undefined levels), set - the game engine version to the actual version; this allows for actual - corrections in the game engine to take effect for existing, converted - levels (from "classic" or other existing games) to make the emulation - of the corresponding game more accurate, while (hopefully) not breaking - existing levels created from other players. */ + header[i * 2 + 0] = header_word >> 8; + header[i * 2 + 1] = header_word & 0xff; + } - level->game_version = GAME_VERSION_ACTUAL; + // read some values from level header to check level decoding integrity + fieldx = header[6] | (header[7] << 8); + fieldy = header[8] | (header[9] << 8); + num_yamyam_contents = header[60] | (header[61] << 8); - /* Set special EM style gems behaviour: EM style gems slip down from - normal, steel and growing wall. As this is a more fundamental change, - it seems better to set the default behaviour to "off" (as it is more - natural) and make it configurable in the level editor (as a property - of gem style elements). Already existing converted levels (neither - private nor contributed levels) are changed to the new behaviour. */ + // do some simple sanity checks to ensure that level was correctly decoded + if (fieldx < 1 || fieldx > 256 || + fieldy < 1 || fieldy > 256 || + num_yamyam_contents < 1 || num_yamyam_contents > 8) + { + level->no_valid_file = TRUE; - if (level->file_version < FILE_VERSION_2_0) - level->em_slippery_gems = TRUE; + Warn("cannot decode level from stream -- using empty level"); return; } - // ---------- use game engine the level was created with -------------------- - - /* For all levels which are not forced to use the latest game engine - version (normally user contributed, private and undefined levels), - use the version of the game engine the levels were created for. - - Since 2.0.1, the game engine version is now directly stored - in the level file (chunk "VERS"), so there is no need anymore - to set the game version from the file version (except for old, - pre-2.0 levels, where the game version is still taken from the - file format version used to store the level -- see above). */ + // maximum envelope header size is 31 bytes + envelope_header_len = header[envelope_header_pos]; + // maximum envelope content size is 110 (156?) bytes + envelope_content_len = header[envelope_content_pos]; - // player was faster than enemies in 1.0.0 and before - if (level->file_version == FILE_VERSION_1_0) - for (i = 0; i < MAX_PLAYERS; i++) - level->initial_player_stepsize[i] = STEPSIZE_FAST; + // maximum level title size is 40 bytes + level_name_len = MIN(header[level_name_pos], MAX_LEVEL_NAME_LEN); + // maximum level author size is 30 (51?) bytes + level_author_len = MIN(header[level_author_pos], MAX_LEVEL_AUTHOR_LEN); - // 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; + envelope_size = 0; - // 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; + for (i = 0; i < envelope_header_len; i++) + if (envelope_size < MAX_ENVELOPE_TEXT_LEN) + level->envelope[0].text[envelope_size++] = + header[envelope_header_pos + 1 + i]; - if (level->game_version < VERSION_IDENT(3,2,0,5)) + if (envelope_header_len > 0 && envelope_content_len > 0) { - // time orb caused limited time in endless time levels before 3.2.0-5 - level->use_time_orb_bug = TRUE; - - // default behaviour for snapping was "no snap delay" before 3.2.0-5 - level->block_snap_field = FALSE; - - // extra time score was same value as time left score before 3.2.0-5 - level->extra_time_score = level->score[SC_TIME_BONUS]; + if (envelope_size < MAX_ENVELOPE_TEXT_LEN) + level->envelope[0].text[envelope_size++] = '\n'; + if (envelope_size < MAX_ENVELOPE_TEXT_LEN) + level->envelope[0].text[envelope_size++] = '\n'; } - if (level->game_version < VERSION_IDENT(3,2,0,7)) - { - // default behaviour for snapping was "not continuous" before 3.2.0-7 - level->continuous_snapping = FALSE; - } + for (i = 0; i < envelope_content_len; i++) + if (envelope_size < MAX_ENVELOPE_TEXT_LEN) + level->envelope[0].text[envelope_size++] = + header[envelope_content_pos + 1 + i]; - // only few elements were able to actively move into acid before 3.1.0 - // trigger settings did not exist before 3.1.0; set to default "any" - if (level->game_version < VERSION_IDENT(3,1,0,0)) - { - // correct "can move into acid" settings (all zero in old levels) + level->envelope[0].text[envelope_size] = '\0'; - level->can_move_into_acid_bits = 0; // nothing can move into acid - level->dont_collide_with_bits = 0; // nothing is deadly when colliding + level->envelope[0].xsize = MAX_ENVELOPE_XSIZE; + level->envelope[0].ysize = 10; + level->envelope[0].autowrap = TRUE; + level->envelope[0].centered = TRUE; - setMoveIntoAcidProperty(level, EL_ROBOT, TRUE); - setMoveIntoAcidProperty(level, EL_SATELLITE, TRUE); - setMoveIntoAcidProperty(level, EL_PENGUIN, TRUE); - setMoveIntoAcidProperty(level, EL_BALLOON, TRUE); + for (i = 0; i < level_name_len; i++) + level->name[i] = header[level_name_pos + 1 + i]; + level->name[level_name_len] = '\0'; - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - SET_PROPERTY(EL_CUSTOM_START + i, EP_CAN_MOVE_INTO_ACID, TRUE); + for (i = 0; i < level_author_len; i++) + level->author[i] = header[level_author_pos + 1 + i]; + level->author[level_author_len] = '\0'; - // correct trigger settings (stored as zero == "none" in old levels) + num_yamyam_contents = header[60] | (header[61] << 8); + level->num_yamyam_contents = + MIN(MAX(MIN_ELEMENT_CONTENTS, num_yamyam_contents), MAX_ELEMENT_CONTENTS); - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + for (i = 0; i < num_yamyam_contents; i++) + { + for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) { - 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]; + unsigned short word = getDecodedWord_DC(getFile16BitBE(file), FALSE); + int element_dc = ((word & 0xff) << 8) | ((word >> 8) & 0xff); - change->trigger_player = CH_PLAYER_ANY; - change->trigger_page = CH_PAGE_ANY; - } + if (i < MAX_ELEMENT_CONTENTS) + level->yamyam_content[i].e[x][y] = getMappedElement_DC(element_dc); } } - // try to detect and fix "Snake Bite" levels, which are broken with 3.2.0 - { - int element = EL_CUSTOM_256; - struct ElementInfo *ei = &element_info[element]; - struct ElementChangeInfo *change = &ei->change_page[0]; + fieldx = header[6] | (header[7] << 8); + fieldy = header[8] | (header[9] << 8); + level->fieldx = MIN(MAX(MIN_LEV_FIELDX, fieldx), MAX_LEV_FIELDX); + level->fieldy = MIN(MAX(MIN_LEV_FIELDY, fieldy), MAX_LEV_FIELDY); - /* This is needed to fix a problem that was caused by a bugfix in function - game.c/CreateFieldExt() introduced with 3.2.0 that corrects the behaviour - when a custom element changes to EL_SOKOBAN_FIELD_PLAYER (before, it did - not replace walkable elements, but instead just placed the player on it, - without placing the Sokoban field under the player). Unfortunately, this - breaks "Snake Bite" style levels when the snake is halfway through a door - that just closes (the snake head is still alive and can be moved in this - case). This can be fixed by replacing the EL_SOKOBAN_FIELD_PLAYER by the - player (without Sokoban element) which then gets killed as designed). */ + for (y = 0; y < fieldy; y++) for (x = 0; x < fieldx; x++) + { + unsigned short word = getDecodedWord_DC(getFile16BitBE(file), FALSE); + int element_dc = ((word & 0xff) << 8) | ((word >> 8) & 0xff); - if ((strncmp(leveldir_current->identifier, "snake_bite", 10) == 0 || - strncmp(ei->description, "pause b4 death", 14) == 0) && - change->target_element == EL_SOKOBAN_FIELD_PLAYER) - change->target_element = EL_PLAYER_1; + if (x < MAX_LEV_FIELDX && y < MAX_LEV_FIELDY) + level->field[x][y] = getMappedElement_DC(element_dc); } - // try to detect and fix "Zelda" style levels, which are broken with 3.2.5 - if (level->game_version < VERSION_IDENT(3,2,5,0)) - { - /* This is needed to fix a problem that was caused by a bugfix in function - game.c/CheckTriggeredElementChangeExt() introduced with 3.2.5 that - corrects the behaviour when a custom element changes to another custom - element with a higher element number that has change actions defined. - Normally, only one change per frame is allowed for custom elements. - Therefore, it is checked if a custom element already changed in the - current frame; if it did, subsequent changes are suppressed. - Unfortunately, this is only checked for element changes, but not for - change actions, which are still executed. As the function above loops - through all custom elements from lower to higher, an element change - resulting in a lower CE number won't be checked again, while a target - element with a higher number will also be checked, and potential change - actions will get executed for this CE, too (which is wrong), while - further changes are ignored (which is correct). As this bugfix breaks - Zelda II (and introduces graphical bugs to Zelda I, and also breaks a - few other levels like Alan Bond's "FMV"), allow the previous, incorrect - behaviour for existing levels and tapes that make use of this bug */ + x = MIN(MAX(0, (header[10] | (header[11] << 8)) - 1), MAX_LEV_FIELDX - 1); + y = MIN(MAX(0, (header[12] | (header[13] << 8)) - 1), MAX_LEV_FIELDY - 1); + level->field[x][y] = EL_PLAYER_1; - level->use_action_after_change_bug = TRUE; - } + x = MIN(MAX(0, (header[14] | (header[15] << 8)) - 1), MAX_LEV_FIELDX - 1); + y = MIN(MAX(0, (header[16] | (header[17] << 8)) - 1), MAX_LEV_FIELDY - 1); + level->field[x][y] = EL_PLAYER_2; - // not centering level after relocating player was default only in 3.2.3 - if (level->game_version == VERSION_IDENT(3,2,3,0)) // (no pre-releases) - level->shifted_relocation = TRUE; + level->gems_needed = header[18] | (header[19] << 8); + + level->score[SC_EMERALD] = header[20] | (header[21] << 8); + level->score[SC_DIAMOND] = header[22] | (header[23] << 8); + level->score[SC_PEARL] = header[24] | (header[25] << 8); + level->score[SC_CRYSTAL] = header[26] | (header[27] << 8); + level->score[SC_NUT] = header[28] | (header[29] << 8); + level->score[SC_ROBOT] = header[30] | (header[31] << 8); + level->score[SC_SPACESHIP] = header[32] | (header[33] << 8); + level->score[SC_BUG] = header[34] | (header[35] << 8); + level->score[SC_YAMYAM] = header[36] | (header[37] << 8); + level->score[SC_DYNAMITE] = header[38] | (header[39] << 8); + level->score[SC_KEY] = header[40] | (header[41] << 8); + level->score[SC_TIME_BONUS] = header[42] | (header[43] << 8); - // EM style elements always chain-exploded in R'n'D engine before 3.2.6 - if (level->game_version < VERSION_IDENT(3,2,6,0)) - level->em_explodes_by_fire = TRUE; + level->time = header[44] | (header[45] << 8); - // levels were solved by the first player entering an exit up to 4.1.0.0 - if (level->game_version <= VERSION_IDENT(4,1,0,0)) - level->solved_by_one_player = TRUE; + level->amoeba_speed = header[46] | (header[47] << 8); + level->time_light = header[48] | (header[49] << 8); + level->time_timegate = header[50] | (header[51] << 8); + level->time_wheel = header[52] | (header[53] << 8); + level->time_magic_wall = header[54] | (header[55] << 8); + level->extra_time = header[56] | (header[57] << 8); + level->shield_normal_time = header[58] | (header[59] << 8); - // game logic of "game of life" and "biomaze" was buggy before 4.1.1.1 - if (level->game_version < VERSION_IDENT(4,1,1,1)) - level->use_life_bugs = TRUE; + // shield and extra time elements do not have a score + level->score[SC_SHIELD] = 0; + level->extra_time_score = 0; - // only Sokoban fields (but not objects) had to be solved before 4.1.1.1 - if (level->game_version < VERSION_IDENT(4,1,1,1)) - level->sb_objects_needed = FALSE; + // set time for normal and deadly shields to the same value + level->shield_deadly_time = level->shield_normal_time; - // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0 - if (level->game_version <= VERSION_IDENT(4,2,2,0)) - level->finish_dig_collect = FALSE; + // Diamond Caves has the same (strange) behaviour as Emerald Mine that gems + // can slip down from flat walls, like normal walls and steel walls + level->em_slippery_gems = TRUE; - // CE changing to player was kept under the player if walkable up to 4.2.3.1 - if (level->game_version <= VERSION_IDENT(4,2,3,1)) - level->keep_walkable_ce = TRUE; + // time score is counted for each 10 seconds left in Diamond Caves levels + level->time_score_base = 10; } -static void LoadLevel_InitSettings_SB(struct LevelInfo *level) +static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - boolean is_sokoban_level = TRUE; // unless non-Sokoban elements found - int x, y; - - // check if this level is (not) a Sokoban level - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) - if (!IS_SB_ELEMENT(Tile[x][y])) - is_sokoban_level = FALSE; + char *filename = level_file_info->filename; + File *file; + int num_magic_bytes = 8; + char magic_bytes[num_magic_bytes + 1]; + int num_levels_to_skip = level_file_info->nr - leveldir_current->first_level; - if (is_sokoban_level) + if (!(file = openFile(filename, MODE_READ))) { - // set special level settings for Sokoban levels - SetLevelSettings_SB(level); + level->no_valid_file = TRUE; + + if (!level_info_only) + Warn("cannot read level '%s' -- using empty level", filename); + + return; } -} -static void LoadLevel_InitSettings(struct LevelInfo *level) -{ - // adjust level settings for (non-native) Sokoban-style levels - LoadLevel_InitSettings_SB(level); -} + // fseek(file, 0x0000, SEEK_SET); -static void LoadLevel_InitStandardElements(struct LevelInfo *level) -{ - int i, x, y; + if (level_file_info->packed) + { + // read "magic bytes" from start of file + if (getStringFromFile(file, magic_bytes, num_magic_bytes + 1) == NULL) + magic_bytes[0] = '\0'; - // 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].e[x][y] = - getMappedElementByVersion(level->yamyam_content[i].e[x][y], - level->game_version); + // check "magic bytes" for correct file format + if (!strPrefix(magic_bytes, "DC2")) + { + level->no_valid_file = TRUE; -} + Warn("unknown DC level file '%s' -- using empty level", filename); -static void LoadLevel_InitCustomElements(struct LevelInfo *level) -{ - int i, j; + return; + } - // map custom element change events that have changed in newer versions - // (these following values were accidentally changed in version 3.0.1) - // (this seems to be needed only for 'ab_levelset3' and 'ab_levelset4') - if (level->game_version <= VERSION_IDENT(3,0,0,0)) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + if (strPrefix(magic_bytes, "DC2Win95") || + strPrefix(magic_bytes, "DC2Win98")) { - int element = EL_CUSTOM_START + i; + int position_first_level = 0x00fa; + int extra_bytes = 4; + int skip_bytes; - // order of checking and copying events to be mapped is important - // (do not change the start and end value -- they are constant) - for (j = CE_BY_OTHER_ACTION; j >= CE_VALUE_GETS_ZERO; j--) - { - if (HAS_CHANGE_EVENT(element, j - 2)) - { - SET_CHANGE_EVENT(element, j - 2, FALSE); - SET_CHANGE_EVENT(element, j, TRUE); - } - } + // advance file stream to first level inside the level package + skip_bytes = position_first_level - num_magic_bytes - extra_bytes; - // order of checking and copying events to be mapped is important - // (do not change the start and end value -- they are constant) - for (j = CE_PLAYER_COLLECTS_X; j >= CE_HITTING_SOMETHING; j--) + // each block of level data is followed by block of non-level data + num_levels_to_skip *= 2; + + // at least skip header bytes, therefore use ">= 0" instead of "> 0" + while (num_levels_to_skip >= 0) { - if (HAS_CHANGE_EVENT(element, j - 1)) + // advance file stream to next level inside the level package + if (seekFile(file, skip_bytes, SEEK_CUR) != 0) { - SET_CHANGE_EVENT(element, j - 1, FALSE); - SET_CHANGE_EVENT(element, j, TRUE); + level->no_valid_file = TRUE; + + Warn("cannot fseek in file '%s' -- using empty level", filename); + + return; } - } - } - } - // initialize "can_change" field for old levels with only one change page - if (level->game_version <= VERSION_IDENT(3,0,2,0)) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; + // skip apparently unused extra bytes following each level + ReadUnusedBytesFromFile(file, extra_bytes); - if (CAN_CHANGE(element)) - element_info[element].change->can_change = TRUE; - } - } + // read size of next level in level package + skip_bytes = getFile32BitLE(file); - // correct custom element values (for old levels without these options) - if (level->game_version < VERSION_IDENT(3,1,1,0)) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + num_levels_to_skip--; + } + } + else { - int element = EL_CUSTOM_START + i; - struct ElementInfo *ei = &element_info[element]; + level->no_valid_file = TRUE; - if (ei->access_direction == MV_NO_DIRECTION) - ei->access_direction = MV_ALL_DIRECTIONS; + Warn("unknown DC2 level file '%s' -- using empty level", filename); + + return; } } - // correct custom element values (fix invalid values for all versions) - if (1) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; - struct ElementInfo *ei = &element_info[element]; + LoadLevelFromFileStream_DC(file, level); - for (j = 0; j < ei->num_change_pages; j++) - { - struct ElementChangeInfo *change = &ei->change_page[j]; + closeFile(file); +} - if (change->trigger_player == CH_PLAYER_NONE) - change->trigger_player = CH_PLAYER_ANY; - if (change->trigger_side == CH_SIDE_NONE) - change->trigger_side = CH_SIDE_ANY; - } - } - } +// ---------------------------------------------------------------------------- +// functions for loading SB level +// ---------------------------------------------------------------------------- - // 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)) +int getMappedElement_SB(int element_ascii, boolean use_ces) +{ + static struct { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; + int ascii; + int sb; + int ce; + } + sb_element_mapping[] = + { + { ' ', EL_EMPTY, EL_CUSTOM_1 }, // floor (space) + { '#', EL_STEELWALL, EL_CUSTOM_2 }, // wall + { '@', EL_PLAYER_1, EL_CUSTOM_3 }, // player + { '$', EL_SOKOBAN_OBJECT, EL_CUSTOM_4 }, // box + { '.', EL_SOKOBAN_FIELD_EMPTY, EL_CUSTOM_5 }, // goal square + { '*', EL_SOKOBAN_FIELD_FULL, EL_CUSTOM_6 }, // box on goal square + { '+', EL_SOKOBAN_FIELD_PLAYER, EL_CUSTOM_7 }, // player on goal square + { '_', EL_INVISIBLE_STEELWALL, EL_FROM_LEVEL_TEMPLATE }, // floor beyond border - if (EXPLODES_1X1_OLD(element)) - element_info[element].explosion_type = EXPLODES_1X1; + { 0, -1, -1 }, + }; - SET_PROPERTY(element, EP_CAN_EXPLODE, (EXPLODES_BY_FIRE(element) || - EXPLODES_SMASHED(element) || - EXPLODES_IMPACT(element))); - } - } + int i; - // 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; + for (i = 0; sb_element_mapping[i].ascii != 0; i++) + if (element_ascii == sb_element_mapping[i].ascii) + return (use_ces ? sb_element_mapping[i].ce : sb_element_mapping[i].sb); + + return EL_UNDEFINED; +} + +static void SetLevelSettings_SB(struct LevelInfo *level) +{ + // time settings + level->time = 0; + level->use_step_counter = TRUE; - 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; - } - } - } + // score settings + level->score[SC_TIME_BONUS] = 0; + level->time_score_base = 1; + level->rate_time_over_score = TRUE; - // set some other uninitialized values of custom elements in older levels - if (level->game_version < VERSION_IDENT(3,1,0,0)) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; + // game settings + level->auto_exit_sokoban = TRUE; +} - element_info[element].access_direction = MV_ALL_DIRECTIONS; +static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) +{ + char *filename = level_file_info->filename; + char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN]; + char last_comment[MAX_LINE_LEN]; + char level_name[MAX_LINE_LEN]; + char *line_ptr; + File *file; + int num_levels_to_skip = level_file_info->nr - leveldir_current->first_level; + boolean read_continued_line = FALSE; + boolean reading_playfield = FALSE; + boolean got_valid_playfield_line = FALSE; + boolean invalid_playfield_char = FALSE; + boolean load_xsb_to_ces = check_special_flags("load_xsb_to_ces"); + int file_level_nr = 0; + int x = 0, y = 0; // initialized to make compilers happy - element_info[element].explosion_delay = 17; - element_info[element].ignition_delay = 8; - } - } + last_comment[0] = '\0'; + level_name[0] = '\0'; - // set mouse click change events to work for left/middle/right mouse button - if (level->game_version < VERSION_IDENT(4,2,3,0)) + if (!(file = openFile(filename, MODE_READ))) { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; - struct ElementInfo *ei = &element_info[element]; + level->no_valid_file = TRUE; - for (j = 0; j < ei->num_change_pages; j++) - { - struct ElementChangeInfo *change = &ei->change_page[j]; + if (!level_info_only) + Warn("cannot read level '%s' -- using empty level", filename); - if (change->has_event[CE_CLICKED_BY_MOUSE] || - change->has_event[CE_PRESSED_BY_MOUSE] || - change->has_event[CE_MOUSE_CLICKED_ON_X] || - change->has_event[CE_MOUSE_PRESSED_ON_X]) - change->trigger_side = CH_SIDE_ANY; - } - } + return; } -} -static void LoadLevel_InitElements(struct LevelInfo *level) -{ - LoadLevel_InitStandardElements(level); + while (!checkEndOfFile(file)) + { + // level successfully read, but next level may follow here + if (!got_valid_playfield_line && reading_playfield) + { + // read playfield from single level file -- skip remaining file + if (!level_file_info->packed) + break; - if (level->file_has_custom_elements) - LoadLevel_InitCustomElements(level); + if (file_level_nr >= num_levels_to_skip) + break; - // initialize element properties for level editor etc. - InitElementPropertiesEngine(level->game_version); - InitElementPropertiesGfxElement(); -} + file_level_nr++; -static void LoadLevel_InitPlayfield(struct LevelInfo *level) -{ - int x, y; + last_comment[0] = '\0'; + level_name[0] = '\0'; - // map elements that have changed in newer versions - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) - level->field[x][y] = getMappedElementByVersion(level->field[x][y], - level->game_version); + reading_playfield = FALSE; + } - // clear unused playfield data (nicer if level gets resized in editor) - for (x = 0; x < MAX_LEV_FIELDX; x++) - for (y = 0; y < MAX_LEV_FIELDY; y++) - if (x >= level->fieldx || y >= level->fieldy) - level->field[x][y] = EL_EMPTY; + got_valid_playfield_line = FALSE; - // copy elements to runtime playfield array - for (x = 0; x < MAX_LEV_FIELDX; x++) - for (y = 0; y < MAX_LEV_FIELDY; y++) - Tile[x][y] = level->field[x][y]; + // read next line of input file + if (!getStringFromFile(file, line, MAX_LINE_LEN)) + break; - // initialize level size variables for faster access - lev_fieldx = level->fieldx; - lev_fieldy = level->fieldy; + // cut trailing line break (this can be newline and/or carriage return) + for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--) + if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0') + *line_ptr = '\0'; - // determine border element for this level - if (level->file_info.type == LEVEL_FILE_TYPE_DC) - BorderElement = EL_EMPTY; // (in editor, SetBorderElement() is used) - else - SetBorderElement(); -} + // copy raw input line for later use (mainly debugging output) + strcpy(line_raw, line); -static void LoadLevel_InitNativeEngines(struct LevelInfo *level) -{ - struct LevelFileInfo *level_file_info = &level->file_info; + if (read_continued_line) + { + // append new line to existing line, if there is enough space + if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN) + strcat(previous_line, line_ptr); - if (level_file_info->type == LEVEL_FILE_TYPE_RND) - CopyNativeLevel_RND_to_Native(level); -} + strcpy(line, previous_line); // copy storage buffer to line -static void LoadLevelTemplate_LoadAndInit(void) -{ - LoadLevelFromFileInfo(&level_template, &level_template.file_info, FALSE); + read_continued_line = FALSE; + } - LoadLevel_InitVersion(&level_template); - LoadLevel_InitElements(&level_template); - LoadLevel_InitSettings(&level_template); + // if the last character is '\', continue at next line + if (strlen(line) > 0 && line[strlen(line) - 1] == '\\') + { + line[strlen(line) - 1] = '\0'; // cut off trailing backslash + strcpy(previous_line, line); // copy line to storage buffer - ActivateLevelTemplate(); -} + read_continued_line = TRUE; -void LoadLevelTemplate(int nr) -{ - if (!fileExists(getGlobalLevelTemplateFilename())) - { - Warn("no level template found for this level"); + continue; + } - return; - } + // skip empty lines + if (line[0] == '\0') + continue; - setLevelFileInfo(&level_template.file_info, nr); + // extract comment text from comment line + if (line[0] == ';') + { + for (line_ptr = line; *line_ptr; line_ptr++) + if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != ';') + break; - LoadLevelTemplate_LoadAndInit(); -} + strcpy(last_comment, line_ptr); -static void LoadNetworkLevelTemplate(struct NetworkLevelInfo *network_level) -{ - copyLevelFileInfo(&network_level->tmpl_info, &level_template.file_info); + continue; + } - LoadLevelTemplate_LoadAndInit(); -} + // extract level title text from line containing level title + if (line[0] == '\'') + { + strcpy(level_name, &line[1]); -static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level) -{ - LoadLevelFromFileInfo(&level, &level.file_info, FALSE); + if (strlen(level_name) > 0 && level_name[strlen(level_name) - 1] == '\'') + level_name[strlen(level_name) - 1] = '\0'; - if (level.use_custom_template) - { - if (network_level != NULL) - LoadNetworkLevelTemplate(network_level); - else - LoadLevelTemplate(-1); - } + continue; + } - LoadLevel_InitVersion(&level); - LoadLevel_InitElements(&level); - LoadLevel_InitPlayfield(&level); - LoadLevel_InitSettings(&level); + // skip lines containing only spaces (or empty lines) + for (line_ptr = line; *line_ptr; line_ptr++) + if (*line_ptr != ' ') + break; + if (*line_ptr == '\0') + continue; - LoadLevel_InitNativeEngines(&level); -} + // at this point, we have found a line containing part of a playfield -void LoadLevel(int nr) -{ - SetLevelSetInfo(leveldir_current->identifier, nr); + got_valid_playfield_line = TRUE; - setLevelFileInfo(&level.file_info, nr); + if (!reading_playfield) + { + reading_playfield = TRUE; + invalid_playfield_char = FALSE; - LoadLevel_LoadAndInit(NULL); -} + for (x = 0; x < MAX_LEV_FIELDX; x++) + for (y = 0; y < MAX_LEV_FIELDY; y++) + level->field[x][y] = getMappedElement_SB(' ', load_xsb_to_ces); -void LoadLevelInfoOnly(int nr) -{ - setLevelFileInfo(&level.file_info, nr); + level->fieldx = 0; + level->fieldy = 0; - LoadLevelFromFileInfo(&level, &level.file_info, TRUE); -} + // start with topmost tile row + y = 0; + } -void LoadNetworkLevel(struct NetworkLevelInfo *network_level) -{ - SetLevelSetInfo(network_level->leveldir_identifier, - network_level->file_info.nr); + // skip playfield line if larger row than allowed + if (y >= MAX_LEV_FIELDY) + continue; - copyLevelFileInfo(&network_level->file_info, &level.file_info); + // start with leftmost tile column + x = 0; - LoadLevel_LoadAndInit(network_level); -} + // read playfield elements from line + for (line_ptr = line; *line_ptr; line_ptr++) + { + int mapped_sb_element = getMappedElement_SB(*line_ptr, load_xsb_to_ces); -static int SaveLevel_VERS(FILE *file, struct LevelInfo *level) -{ - int chunk_size = 0; + // stop parsing playfield line if larger column than allowed + if (x >= MAX_LEV_FIELDX) + break; - chunk_size += putFileVersion(file, level->file_version); - chunk_size += putFileVersion(file, level->game_version); + if (mapped_sb_element == EL_UNDEFINED) + { + invalid_playfield_char = TRUE; - return chunk_size; -} + break; + } -static int SaveLevel_DATE(FILE *file, struct LevelInfo *level) -{ - int chunk_size = 0; + level->field[x][y] = mapped_sb_element; - chunk_size += putFile16BitBE(file, level->creation_date.year); - chunk_size += putFile8Bit(file, level->creation_date.month); - chunk_size += putFile8Bit(file, level->creation_date.day); + // continue with next tile column + x++; - return chunk_size; -} + level->fieldx = MAX(x, level->fieldx); + } -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level) -{ - int i, x, y; + if (invalid_playfield_char) + { + // if first playfield line, treat invalid lines as comment lines + if (y == 0) + reading_playfield = FALSE; - putFile8Bit(file, level->fieldx); - putFile8Bit(file, level->fieldy); + continue; + } - putFile16BitBE(file, level->time); - putFile16BitBE(file, level->gems_needed); + // continue with next tile row + y++; + } - for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) - putFile8Bit(file, level->name[i]); + closeFile(file); - for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) - putFile8Bit(file, level->score[i]); + level->fieldy = y; - for (i = 0; i < STD_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile8Bit(file, (level->encoding_16bit_yamyam ? EL_EMPTY : - level->yamyam_content[i].e[x][y])); - putFile8Bit(file, level->amoeba_speed); - putFile8Bit(file, level->time_magic_wall); - putFile8Bit(file, level->time_wheel); - putFile8Bit(file, (level->encoding_16bit_amoeba ? EL_EMPTY : - level->amoeba_content)); - putFile8Bit(file, (level->initial_player_stepsize == STEPSIZE_FAST ? 1 : 0)); - putFile8Bit(file, (level->initial_gravity ? 1 : 0)); - putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0)); - putFile8Bit(file, (level->em_slippery_gems ? 1 : 0)); + level->fieldx = MIN(MAX(MIN_LEV_FIELDX, level->fieldx), MAX_LEV_FIELDX); + level->fieldy = MIN(MAX(MIN_LEV_FIELDY, level->fieldy), MAX_LEV_FIELDY); - putFile8Bit(file, (level->use_custom_template ? 1 : 0)); + if (!reading_playfield) + { + level->no_valid_file = TRUE; - 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); + Warn("cannot read level '%s' -- using empty level", filename); - putFile8Bit(file, (level->use_spring_bug ? 1 : 0)); - putFile8Bit(file, (level->use_step_counter ? 1 : 0)); + return; + } - putFile8Bit(file, (level->instant_relocation ? 1 : 0)); - putFile8Bit(file, (level->can_pass_to_walkable ? 1 : 0)); - putFile8Bit(file, (level->grow_into_diggable ? 1 : 0)); + if (*level_name != '\0') + { + strncpy(level->name, level_name, MAX_LEVEL_NAME_LEN); + level->name[MAX_LEVEL_NAME_LEN] = '\0'; + } + else if (*last_comment != '\0') + { + strncpy(level->name, last_comment, MAX_LEVEL_NAME_LEN); + level->name[MAX_LEVEL_NAME_LEN] = '\0'; + } + else + { + sprintf(level->name, "--> Level %d <--", level_file_info->nr); + } - putFile8Bit(file, level->game_engine_type); + // set all empty fields beyond the border walls to invisible steel wall + for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++) + { + if ((x == 0 || x == level->fieldx - 1 || + y == 0 || y == level->fieldy - 1) && + level->field[x][y] == getMappedElement_SB(' ', load_xsb_to_ces)) + FloodFillLevel(x, y, getMappedElement_SB('_', load_xsb_to_ces), + level->field, level->fieldx, level->fieldy); + } - WriteUnusedBytesToFile(file, LEVEL_CHUNK_HEAD_UNUSED); -} -#endif + // set special level settings for Sokoban levels + SetLevelSettings_SB(level); -static int SaveLevel_NAME(FILE *file, struct LevelInfo *level) -{ - int chunk_size = 0; - int i; + if (load_xsb_to_ces) + { + // special global settings can now be set in level template + level->use_custom_template = TRUE; + } +} - for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) - chunk_size += putFile8Bit(file, level->name[i]); - return chunk_size; -} +// ------------------------------------------------------------------------- +// functions for handling native levels +// ------------------------------------------------------------------------- -static int SaveLevel_AUTH(FILE *file, struct LevelInfo *level) +static void LoadLevelFromFileInfo_BD(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - int chunk_size = 0; - int i; + int pos = 0; - for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) - chunk_size += putFile8Bit(file, level->author[i]); + // determine position of requested level inside level package + if (level_file_info->packed) + pos = level_file_info->nr - leveldir_current->first_level; - return chunk_size; + if (!LoadNativeLevel_BD(level_file_info->filename, pos, level_info_only)) + level->no_valid_file = TRUE; } -#if ENABLE_HISTORIC_CHUNKS -static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) +static void LoadLevelFromFileInfo_EM(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - int chunk_size = 0; - int x, y; - - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) - if (level->encoding_16bit_field) - chunk_size += putFile16BitBE(file, level->field[x][y]); - else - chunk_size += putFile8Bit(file, level->field[x][y]); - - return chunk_size; + if (!LoadNativeLevel_EM(level_file_info->filename, level_info_only)) + level->no_valid_file = TRUE; } -#endif -static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) +static void LoadLevelFromFileInfo_SP(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - int chunk_size = 0; - int x, y; + int pos = 0; - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) - chunk_size += putFile16BitBE(file, level->field[x][y]); + // determine position of requested level inside level package + if (level_file_info->packed) + pos = level_file_info->nr - leveldir_current->first_level; - return chunk_size; + if (!LoadNativeLevel_SP(level_file_info->filename, pos, level_info_only)) + level->no_valid_file = TRUE; } -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CONT(FILE *file, struct LevelInfo *level) +static void LoadLevelFromFileInfo_MM(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) { - int i, x, y; - - putFile8Bit(file, EL_YAMYAM); - putFile8Bit(file, level->num_yamyam_contents); - putFile8Bit(file, 0); - putFile8Bit(file, 0); - - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - if (level->encoding_16bit_field) - putFile16BitBE(file, level->yamyam_content[i].e[x][y]); - else - putFile8Bit(file, level->yamyam_content[i].e[x][y]); + if (!LoadNativeLevel_MM(level_file_info->filename, level_info_only)) + level->no_valid_file = TRUE; } -#endif -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) +void CopyNativeLevel_RND_to_Native(struct LevelInfo *level) { - int i, x, y; - int num_contents, content_xsize, content_ysize; - int content_array[MAX_ELEMENT_CONTENTS][3][3]; - - if (element == EL_YAMYAM) - { - num_contents = level->num_yamyam_contents; - content_xsize = 3; - content_ysize = 3; - - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content_array[i][x][y] = level->yamyam_content[i].e[x][y]; - } - else if (element == EL_BD_AMOEBA) - { - num_contents = 1; - content_xsize = 1; - content_ysize = 1; - - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - content_array[i][x][y] = EL_EMPTY; - content_array[0][0][0] = level->amoeba_content; - } - else - { - // chunk header already written -- write empty chunk data - WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE); - - Warn("cannot save content for element '%d'", element); - - return; - } - - putFile16BitBE(file, element); - putFile8Bit(file, num_contents); - putFile8Bit(file, content_xsize); - putFile8Bit(file, content_ysize); - - WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_UNUSED); - - for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile16BitBE(file, content_array[i][x][y]); + if (level->game_engine_type == GAME_ENGINE_TYPE_BD) + CopyNativeLevel_RND_to_BD(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_EM) + CopyNativeLevel_RND_to_EM(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) + CopyNativeLevel_RND_to_SP(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_MM) + CopyNativeLevel_RND_to_MM(level); } -#endif -#if ENABLE_HISTORIC_CHUNKS -static int SaveLevel_CNT3(FILE *file, struct LevelInfo *level, int element) +void CopyNativeLevel_Native_to_RND(struct LevelInfo *level) { - int envelope_nr = element - EL_ENVELOPE_1; - int envelope_len = strlen(level->envelope_text[envelope_nr]) + 1; - int chunk_size = 0; - int i; - - chunk_size += putFile16BitBE(file, element); - chunk_size += putFile16BitBE(file, envelope_len); - chunk_size += putFile8Bit(file, level->envelope_xsize[envelope_nr]); - chunk_size += putFile8Bit(file, level->envelope_ysize[envelope_nr]); - - WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT3_UNUSED); - chunk_size += LEVEL_CHUNK_CNT3_UNUSED; - - for (i = 0; i < envelope_len; i++) - chunk_size += putFile8Bit(file, level->envelope_text[envelope_nr][i]); - - return chunk_size; + if (level->game_engine_type == GAME_ENGINE_TYPE_BD) + CopyNativeLevel_BD_to_RND(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_EM) + CopyNativeLevel_EM_to_RND(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) + CopyNativeLevel_SP_to_RND(level); + else if (level->game_engine_type == GAME_ENGINE_TYPE_MM) + CopyNativeLevel_MM_to_RND(level); } -#endif -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level, - int num_changed_custom_elements) +void SaveNativeLevel(struct LevelInfo *level) { - int i, check = 0; + // saving native level files only supported for some game engines + if (level->game_engine_type != GAME_ENGINE_TYPE_BD && + level->game_engine_type != GAME_ENGINE_TYPE_SP) + return; - putFile16BitBE(file, num_changed_custom_elements); + char *file_ext = (level->game_engine_type == GAME_ENGINE_TYPE_BD ? "bd" : + level->game_engine_type == GAME_ENGINE_TYPE_SP ? "sp" : ""); + char *basename = getSingleLevelBasenameExt(level->file_info.nr, file_ext); + char *filename = getLevelFilenameFromBasename(basename); - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; + if (fileExists(filename) && !Request("Native level file already exists! Overwrite it?", REQ_ASK)) + return; - struct ElementInfo *ei = &element_info[element]; + boolean success = FALSE; - if (ei->properties[EP_BITFIELD_BASE_NR] != EP_BITMASK_DEFAULT) - { - if (check < num_changed_custom_elements) - { - putFile16BitBE(file, element); - putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); - } + if (level->game_engine_type == GAME_ENGINE_TYPE_BD) + { + CopyNativeLevel_RND_to_BD(level); + // CopyNativeTape_RND_to_BD(level); - check++; - } + success = SaveNativeLevel_BD(filename); } + else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) + { + CopyNativeLevel_RND_to_SP(level); + CopyNativeTape_RND_to_SP(level); - if (check != num_changed_custom_elements) // should not happen - Warn("inconsistent number of custom element properties"); + success = SaveNativeLevel_SP(filename); + } + + if (success) + Request("Native level file saved!", REQ_CONFIRM); + else + Request("Failed to save native level file!", REQ_CONFIRM); } -#endif -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level, - int num_changed_custom_elements) -{ - int i, check = 0; - putFile16BitBE(file, num_changed_custom_elements); +// ---------------------------------------------------------------------------- +// functions for loading generic level +// ---------------------------------------------------------------------------- - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) +static void LoadLevelFromFileInfo(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) +{ + // always start with reliable default values + setLevelInfoToDefaults(level, level_info_only, TRUE); + + switch (level_file_info->type) { - int element = EL_CUSTOM_START + i; + case LEVEL_FILE_TYPE_RND: + LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only); + break; - if (element_info[element].change->target_element != EL_EMPTY_SPACE) - { - if (check < num_changed_custom_elements) - { - putFile16BitBE(file, element); - putFile16BitBE(file, element_info[element].change->target_element); - } + case LEVEL_FILE_TYPE_BD: + LoadLevelFromFileInfo_BD(level, level_file_info, level_info_only); + level->game_engine_type = GAME_ENGINE_TYPE_BD; + break; - check++; - } - } + case LEVEL_FILE_TYPE_EM: + LoadLevelFromFileInfo_EM(level, level_file_info, level_info_only); + level->game_engine_type = GAME_ENGINE_TYPE_EM; + break; - if (check != num_changed_custom_elements) // should not happen - Warn("inconsistent number of custom target elements"); -} -#endif + case LEVEL_FILE_TYPE_SP: + LoadLevelFromFileInfo_SP(level, level_file_info, level_info_only); + level->game_engine_type = GAME_ENGINE_TYPE_SP; + break; -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, - int num_changed_custom_elements) -{ - int i, j, x, y, check = 0; + case LEVEL_FILE_TYPE_MM: + LoadLevelFromFileInfo_MM(level, level_file_info, level_info_only); + level->game_engine_type = GAME_ENGINE_TYPE_MM; + break; - putFile16BitBE(file, num_changed_custom_elements); + case LEVEL_FILE_TYPE_DC: + LoadLevelFromFileInfo_DC(level, level_file_info, level_info_only); + break; - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; - struct ElementInfo *ei = &element_info[element]; + case LEVEL_FILE_TYPE_SB: + LoadLevelFromFileInfo_SB(level, level_file_info, level_info_only); + break; - if (ei->modified_settings) - { - if (check < num_changed_custom_elements) - { - putFile16BitBE(file, element); + default: + LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only); + break; + } - for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++) - putFile8Bit(file, ei->description[j]); + // if level file is invalid, restore level structure to default values + if (level->no_valid_file) + setLevelInfoToDefaults(level, level_info_only, FALSE); - putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); + if (check_special_flags("use_native_bd_game_engine")) + level->game_engine_type = GAME_ENGINE_TYPE_BD; - // some free bytes for future properties and padding - WriteUnusedBytesToFile(file, 7); + if (level->game_engine_type == GAME_ENGINE_TYPE_UNKNOWN) + level->game_engine_type = GAME_ENGINE_TYPE_RND; - putFile8Bit(file, ei->use_gfx_element); - putFile16BitBE(file, ei->gfx_element_initial); + if (level_file_info->type != LEVEL_FILE_TYPE_RND) + CopyNativeLevel_Native_to_RND(level); +} - putFile8Bit(file, ei->collect_score_initial); - putFile8Bit(file, ei->collect_count_initial); +void LoadLevelFromFilename(struct LevelInfo *level, char *filename) +{ + static struct LevelFileInfo level_file_info; - putFile16BitBE(file, ei->push_delay_fixed); - putFile16BitBE(file, ei->push_delay_random); - putFile16BitBE(file, ei->move_delay_fixed); - putFile16BitBE(file, ei->move_delay_random); + // always start with reliable default values + setFileInfoToDefaults(&level_file_info); - putFile16BitBE(file, ei->move_pattern); - putFile8Bit(file, ei->move_direction_initial); - putFile8Bit(file, ei->move_stepsize); + level_file_info.nr = 0; // unknown level number + level_file_info.type = LEVEL_FILE_TYPE_RND; // no others supported yet - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile16BitBE(file, ei->content.e[x][y]); + setString(&level_file_info.filename, filename); - putFile32BitBE(file, ei->change->events); + LoadLevelFromFileInfo(level, &level_file_info, FALSE); +} - putFile16BitBE(file, ei->change->target_element); +static void LoadLevel_InitVersion(struct LevelInfo *level) +{ + int i, j; - putFile16BitBE(file, ei->change->delay_fixed); - putFile16BitBE(file, ei->change->delay_random); - putFile16BitBE(file, ei->change->delay_frames); + if (leveldir_current == NULL) // only when dumping level + return; - putFile16BitBE(file, ei->change->initial_trigger_element); + // all engine modifications also valid for levels which use latest engine + if (level->game_version < VERSION_IDENT(3,2,0,5)) + { + // time bonus score was given for 10 s instead of 1 s before 3.2.0-5 + level->time_score_base = 10; + } - putFile8Bit(file, ei->change->explode); - putFile8Bit(file, ei->change->use_target_content); - putFile8Bit(file, ei->change->only_if_complete); - putFile8Bit(file, ei->change->use_random_replace); + if (leveldir_current->latest_engine) + { + // ---------- use latest game engine -------------------------------------- - putFile8Bit(file, ei->change->random_percentage); - putFile8Bit(file, ei->change->replace_when); + /* For all levels which are forced to use the latest game engine version + (normally all but user contributed, private and undefined levels), set + the game engine version to the actual version; this allows for actual + corrections in the game engine to take effect for existing, converted + levels (from "classic" or other existing games) to make the emulation + of the corresponding game more accurate, while (hopefully) not breaking + existing levels created from other players. */ - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile16BitBE(file, ei->change->content.e[x][y]); + level->game_version = GAME_VERSION_ACTUAL; - putFile8Bit(file, ei->slippery_type); + /* Set special EM style gems behaviour: EM style gems slip down from + normal, steel and growing wall. As this is a more fundamental change, + it seems better to set the default behaviour to "off" (as it is more + natural) and make it configurable in the level editor (as a property + of gem style elements). Already existing converted levels (neither + private nor contributed levels) are changed to the new behaviour. */ - // some free bytes for future properties and padding - WriteUnusedBytesToFile(file, LEVEL_CPART_CUS3_UNUSED); - } + if (level->file_version < FILE_VERSION_2_0) + level->em_slippery_gems = TRUE; - check++; - } + return; } - if (check != num_changed_custom_elements) // should not happen - Warn("inconsistent number of custom element properties"); -} -#endif + // ---------- use game engine the level was created with -------------------- -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) -{ - struct ElementInfo *ei = &element_info[element]; - int i, j, x, y; + /* For all levels which are not forced to use the latest game engine + version (normally user contributed, private and undefined levels), + use the version of the game engine the levels were created for. - // ---------- custom element base property values (96 bytes) ---------------- + Since 2.0.1, the game engine version is now directly stored + in the level file (chunk "VERS"), so there is no need anymore + to set the game version from the file version (except for old, + pre-2.0 levels, where the game version is still taken from the + file format version used to store the level -- see above). */ + + // player was faster than enemies in 1.0.0 and before + if (level->file_version == FILE_VERSION_1_0) + for (i = 0; i < MAX_PLAYERS; i++) + level->initial_player_stepsize[i] = STEPSIZE_FAST; + + // 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; + + if (level->game_version < VERSION_IDENT(3,2,0,5)) + { + // time orb caused limited time in endless time levels before 3.2.0-5 + level->use_time_orb_bug = TRUE; - putFile16BitBE(file, element); + // default behaviour for snapping was "no snap delay" before 3.2.0-5 + level->block_snap_field = FALSE; - for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) - putFile8Bit(file, ei->description[i]); + // extra time score was same value as time left score before 3.2.0-5 + level->extra_time_score = level->score[SC_TIME_BONUS]; + } - putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); + if (level->game_version < VERSION_IDENT(3,2,0,7)) + { + // default behaviour for snapping was "not continuous" before 3.2.0-7 + level->continuous_snapping = FALSE; + } - WriteUnusedBytesToFile(file, 4); // reserved for more base properties + // only few elements were able to actively move into acid before 3.1.0 + // trigger settings did not exist before 3.1.0; set to default "any" + if (level->game_version < VERSION_IDENT(3,1,0,0)) + { + // correct "can move into acid" settings (all zero in old levels) - putFile8Bit(file, ei->num_change_pages); + level->can_move_into_acid_bits = 0; // nothing can move into acid + level->dont_collide_with_bits = 0; // nothing is deadly when colliding - putFile16BitBE(file, ei->ce_value_fixed_initial); - putFile16BitBE(file, ei->ce_value_random_initial); - putFile8Bit(file, ei->use_last_ce_value); + setMoveIntoAcidProperty(level, EL_ROBOT, TRUE); + setMoveIntoAcidProperty(level, EL_SATELLITE, TRUE); + setMoveIntoAcidProperty(level, EL_PENGUIN, TRUE); + setMoveIntoAcidProperty(level, EL_BALLOON, TRUE); - putFile8Bit(file, ei->use_gfx_element); - putFile16BitBE(file, ei->gfx_element_initial); + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + SET_PROPERTY(EL_CUSTOM_START + i, EP_CAN_MOVE_INTO_ACID, TRUE); - putFile8Bit(file, ei->collect_score_initial); - putFile8Bit(file, ei->collect_count_initial); + // correct trigger settings (stored as zero == "none" in old levels) - 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); + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; - // bits 0 - 15 of "move_pattern" ... - putFile16BitBE(file, ei->move_pattern & 0xffff); - putFile8Bit(file, ei->move_direction_initial); - putFile8Bit(file, ei->move_stepsize); + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; - putFile8Bit(file, ei->slippery_type); + change->trigger_player = CH_PLAYER_ANY; + change->trigger_page = CH_PAGE_ANY; + } + } + } - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile16BitBE(file, ei->content.e[x][y]); + // try to detect and fix "Snake Bite" levels, which are broken with 3.2.0 + { + int element = EL_CUSTOM_256; + struct ElementInfo *ei = &element_info[element]; + struct ElementChangeInfo *change = &ei->change_page[0]; - putFile16BitBE(file, ei->move_enter_element); - putFile16BitBE(file, ei->move_leave_element); - putFile8Bit(file, ei->move_leave_type); + /* This is needed to fix a problem that was caused by a bugfix in function + game.c/CreateFieldExt() introduced with 3.2.0 that corrects the behaviour + when a custom element changes to EL_SOKOBAN_FIELD_PLAYER (before, it did + not replace walkable elements, but instead just placed the player on it, + without placing the Sokoban field under the player). Unfortunately, this + breaks "Snake Bite" style levels when the snake is halfway through a door + that just closes (the snake head is still alive and can be moved in this + case). This can be fixed by replacing the EL_SOKOBAN_FIELD_PLAYER by the + player (without Sokoban element) which then gets killed as designed). */ - // ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) - putFile16BitBE(file, (ei->move_pattern >> 16) & 0xffff); + if ((strncmp(leveldir_current->identifier, "snake_bite", 10) == 0 || + strncmp(ei->description, "pause b4 death", 14) == 0) && + change->target_element == EL_SOKOBAN_FIELD_PLAYER) + change->target_element = EL_PLAYER_1; + } - putFile8Bit(file, ei->access_direction); + // try to detect and fix "Zelda" style levels, which are broken with 3.2.5 + if (level->game_version < VERSION_IDENT(3,2,5,0)) + { + /* This is needed to fix a problem that was caused by a bugfix in function + game.c/CheckTriggeredElementChangeExt() introduced with 3.2.5 that + corrects the behaviour when a custom element changes to another custom + element with a higher element number that has change actions defined. + Normally, only one change per frame is allowed for custom elements. + Therefore, it is checked if a custom element already changed in the + current frame; if it did, subsequent changes are suppressed. + Unfortunately, this is only checked for element changes, but not for + change actions, which are still executed. As the function above loops + through all custom elements from lower to higher, an element change + resulting in a lower CE number won't be checked again, while a target + element with a higher number will also be checked, and potential change + actions will get executed for this CE, too (which is wrong), while + further changes are ignored (which is correct). As this bugfix breaks + Zelda II (and introduces graphical bugs to Zelda I, and also breaks a + few other levels like Alan Bond's "FMV"), allow the previous, incorrect + behaviour for existing levels and tapes that make use of this bug */ - putFile8Bit(file, ei->explosion_delay); - putFile8Bit(file, ei->ignition_delay); - putFile8Bit(file, ei->explosion_type); + level->use_action_after_change_bug = TRUE; + } - // some free bytes for future custom property values and padding - WriteUnusedBytesToFile(file, 1); + // not centering level after relocating player was default only in 3.2.3 + if (level->game_version == VERSION_IDENT(3,2,3,0)) // (no pre-releases) + level->shifted_relocation = TRUE; - // ---------- change page property values (48 bytes) ------------------------ + // EM style elements always chain-exploded in R'n'D engine before 3.2.6 + if (level->game_version < VERSION_IDENT(3,2,6,0)) + level->em_explodes_by_fire = TRUE; - for (i = 0; i < ei->num_change_pages; i++) - { - struct ElementChangeInfo *change = &ei->change_page[i]; - unsigned int event_bits; + // levels were solved by the first player entering an exit up to 4.1.0.0 + if (level->game_version <= VERSION_IDENT(4,1,0,0)) + level->solved_by_one_player = TRUE; - // bits 0 - 31 of "has_event[]" ... - event_bits = 0; - for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) - if (change->has_event[j]) - event_bits |= (1 << j); - putFile32BitBE(file, event_bits); + // game logic of "game of life" and "biomaze" was buggy before 4.1.1.1 + if (level->game_version < VERSION_IDENT(4,1,1,1)) + level->use_life_bugs = TRUE; - putFile16BitBE(file, change->target_element); + // only Sokoban fields (but not objects) had to be solved before 4.1.1.1 + if (level->game_version < VERSION_IDENT(4,1,1,1)) + level->sb_objects_needed = FALSE; - putFile16BitBE(file, change->delay_fixed); - putFile16BitBE(file, change->delay_random); - putFile16BitBE(file, change->delay_frames); + // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0 + if (level->game_version <= VERSION_IDENT(4,2,2,0)) + level->finish_dig_collect = FALSE; - putFile16BitBE(file, change->initial_trigger_element); + // CE changing to player was kept under the player if walkable up to 4.2.3.1 + if (level->game_version <= VERSION_IDENT(4,2,3,1)) + level->keep_walkable_ce = TRUE; +} - putFile8Bit(file, change->explode); - putFile8Bit(file, change->use_target_content); - putFile8Bit(file, change->only_if_complete); - putFile8Bit(file, change->use_random_replace); +static void LoadLevel_InitSettings_SB(struct LevelInfo *level) +{ + boolean is_sokoban_level = TRUE; // unless non-Sokoban elements found + int x, y; - putFile8Bit(file, change->random_percentage); - putFile8Bit(file, change->replace_when); + // check if this level is (not) a Sokoban level + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + if (!IS_SB_ELEMENT(Tile[x][y])) + is_sokoban_level = FALSE; - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - putFile16BitBE(file, change->target_content.e[x][y]); + if (is_sokoban_level) + { + // set special level settings for Sokoban levels + SetLevelSettings_SB(level); + } +} - putFile8Bit(file, change->can_change); +static void LoadLevel_InitSettings(struct LevelInfo *level) +{ + // adjust level settings for (non-native) Sokoban-style levels + LoadLevel_InitSettings_SB(level); - putFile8Bit(file, change->trigger_side); + // rename levels with title "nameless level" or if renaming is forced + if (leveldir_current->empty_level_name != NULL && + (strEqual(level->name, NAMELESS_LEVEL_NAME) || + leveldir_current->force_level_name)) + snprintf(level->name, MAX_LEVEL_NAME_LEN + 1, + leveldir_current->empty_level_name, level_nr); +} - putFile8Bit(file, change->trigger_player); - putFile8Bit(file, (change->trigger_page == CH_PAGE_ANY ? CH_PAGE_ANY_FILE : - log_2(change->trigger_page))); +static void LoadLevel_InitStandardElements(struct LevelInfo *level) +{ + int i, x, y; - putFile8Bit(file, change->has_action); - putFile8Bit(file, change->action_type); - putFile8Bit(file, change->action_mode); - putFile16BitBE(file, change->action_arg); + // 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].e[x][y] = + getMappedElementByVersion(level->yamyam_content[i].e[x][y], + level->game_version); - // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) - event_bits = 0; - for (j = 32; j < NUM_CHANGE_EVENTS; j++) - if (change->has_event[j]) - event_bits |= (1 << (j - 32)); - putFile8Bit(file, event_bits); - } } -#endif -#if ENABLE_HISTORIC_CHUNKS -static void SaveLevel_GRP1(FILE *file, struct LevelInfo *level, int element) +static void LoadLevel_InitCustomElements(struct LevelInfo *level) { - struct ElementInfo *ei = &element_info[element]; - struct ElementGroupInfo *group = ei->group; - int i; + int i, j; - putFile16BitBE(file, element); + // map custom element change events that have changed in newer versions + // (these following values were accidentally changed in version 3.0.1) + // (this seems to be needed only for 'ab_levelset3' and 'ab_levelset4') + if (level->game_version <= VERSION_IDENT(3,0,0,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + // order of checking and copying events to be mapped is important + // (do not change the start and end value -- they are constant) + for (j = CE_BY_OTHER_ACTION; j >= CE_VALUE_GETS_ZERO; j--) + { + if (HAS_CHANGE_EVENT(element, j - 2)) + { + SET_CHANGE_EVENT(element, j - 2, FALSE); + SET_CHANGE_EVENT(element, j, TRUE); + } + } + + // order of checking and copying events to be mapped is important + // (do not change the start and end value -- they are constant) + for (j = CE_PLAYER_COLLECTS_X; j >= CE_HITTING_SOMETHING; j--) + { + if (HAS_CHANGE_EVENT(element, j - 1)) + { + SET_CHANGE_EVENT(element, j - 1, FALSE); + SET_CHANGE_EVENT(element, j, TRUE); + } + } + } + } + + // initialize "can_change" field for old levels with only one change page + if (level->game_version <= VERSION_IDENT(3,0,2,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; - for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) - putFile8Bit(file, ei->description[i]); + if (CAN_CHANGE(element)) + element_info[element].change->can_change = TRUE; + } + } - putFile8Bit(file, group->num_elements); + // correct custom element values (for old levels without these options) + if (level->game_version < VERSION_IDENT(3,1,1,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; - putFile8Bit(file, ei->use_gfx_element); - putFile16BitBE(file, ei->gfx_element_initial); + if (ei->access_direction == MV_NO_DIRECTION) + ei->access_direction = MV_ALL_DIRECTIONS; + } + } - putFile8Bit(file, group->choice_mode); + // correct custom element values (fix invalid values for all versions) + if (1) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; - // some free bytes for future values and padding - WriteUnusedBytesToFile(file, 3); + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; - for (i = 0; i < MAX_ELEMENTS_IN_GROUP; i++) - putFile16BitBE(file, group->element[i]); -} -#endif + if (change->trigger_player == CH_PLAYER_NONE) + change->trigger_player = CH_PLAYER_ANY; -static int SaveLevel_MicroChunk(FILE *file, struct LevelFileConfigInfo *entry, - boolean write_element) -{ - int save_type = entry->save_type; - int data_type = entry->data_type; - int conf_type = entry->conf_type; - int byte_mask = conf_type & CONF_MASK_BYTES; - int element = entry->element; - int default_value = entry->default_value; - int num_bytes = 0; - boolean modified = FALSE; + if (change->trigger_side == CH_SIDE_NONE) + change->trigger_side = CH_SIDE_ANY; + } + } + } - if (byte_mask != CONF_MASK_MULTI_BYTES) + // 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)) { - void *value_ptr = entry->value; - int value = (data_type == TYPE_BOOLEAN ? *(boolean *)value_ptr : - *(int *)value_ptr); + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; - // check if any settings have been modified before saving them - if (value != default_value) - modified = TRUE; + if (EXPLODES_1X1_OLD(element)) + element_info[element].explosion_type = EXPLODES_1X1; - // do not save if explicitly told or if unmodified default settings - if ((save_type == SAVE_CONF_NEVER) || - (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) - return 0; + SET_PROPERTY(element, EP_CAN_EXPLODE, (EXPLODES_BY_FIRE(element) || + EXPLODES_SMASHED(element) || + EXPLODES_IMPACT(element))); + } + } - if (write_element) - num_bytes += putFile16BitBE(file, 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; - num_bytes += putFile8Bit(file, conf_type); - num_bytes += (byte_mask == CONF_MASK_1_BYTE ? putFile8Bit (file, value) : - byte_mask == CONF_MASK_2_BYTE ? putFile16BitBE(file, value) : - byte_mask == CONF_MASK_4_BYTE ? putFile32BitBE(file, value) : - 0); + 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; + } + } } - else if (data_type == TYPE_STRING) + + // set some other uninitialized values of custom elements in older levels + if (level->game_version < VERSION_IDENT(3,1,0,0)) { - char *default_string = entry->default_string; - char *string = (char *)(entry->value); - int string_length = strlen(string); - int i; + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; - // check if any settings have been modified before saving them - if (!strEqual(string, default_string)) - modified = TRUE; + element_info[element].access_direction = MV_ALL_DIRECTIONS; - // do not save if explicitly told or if unmodified default settings - if ((save_type == SAVE_CONF_NEVER) || - (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) - return 0; + element_info[element].explosion_delay = 17; + element_info[element].ignition_delay = 8; + } + } - if (write_element) - num_bytes += putFile16BitBE(file, element); + // set mouse click change events to work for left/middle/right mouse button + if (level->game_version < VERSION_IDENT(4,2,3,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; - num_bytes += putFile8Bit(file, conf_type); - num_bytes += putFile16BitBE(file, string_length); + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; - for (i = 0; i < string_length; i++) - num_bytes += putFile8Bit(file, string[i]); + if (change->has_event[CE_CLICKED_BY_MOUSE] || + change->has_event[CE_PRESSED_BY_MOUSE] || + change->has_event[CE_MOUSE_CLICKED_ON_X] || + change->has_event[CE_MOUSE_PRESSED_ON_X]) + change->trigger_side = CH_SIDE_ANY; + } + } } - else if (data_type == TYPE_ELEMENT_LIST) - { - int *element_array = (int *)(entry->value); - int num_elements = *(int *)(entry->num_entities); - int i; +} - // check if any settings have been modified before saving them - for (i = 0; i < num_elements; i++) - if (element_array[i] != default_value) - modified = TRUE; +static void LoadLevel_InitElements(struct LevelInfo *level) +{ + LoadLevel_InitStandardElements(level); - // do not save if explicitly told or if unmodified default settings - if ((save_type == SAVE_CONF_NEVER) || - (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) - return 0; + if (level->file_has_custom_elements) + LoadLevel_InitCustomElements(level); - if (write_element) - num_bytes += putFile16BitBE(file, element); + // initialize element properties for level editor etc. + InitElementPropertiesEngine(level->game_version); + InitElementPropertiesGfxElement(); +} - num_bytes += putFile8Bit(file, conf_type); - num_bytes += putFile16BitBE(file, num_elements * CONF_ELEMENT_NUM_BYTES); +static void LoadLevel_InitPlayfield(struct LevelInfo *level) +{ + int x, y; - for (i = 0; i < num_elements; i++) - num_bytes += putFile16BitBE(file, element_array[i]); - } - else if (data_type == TYPE_CONTENT_LIST) - { - struct Content *content = (struct Content *)(entry->value); - int num_contents = *(int *)(entry->num_entities); - int i, x, y; + // map elements that have changed in newer versions + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + level->field[x][y] = getMappedElementByVersion(level->field[x][y], + level->game_version); - // check if any settings have been modified before saving them - for (i = 0; i < num_contents; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - if (content[i].e[x][y] != default_value) - modified = TRUE; + // clear unused playfield data (nicer if level gets resized in editor) + for (x = 0; x < MAX_LEV_FIELDX; x++) + for (y = 0; y < MAX_LEV_FIELDY; y++) + if (x >= level->fieldx || y >= level->fieldy) + level->field[x][y] = EL_EMPTY; - // do not save if explicitly told or if unmodified default settings - if ((save_type == SAVE_CONF_NEVER) || - (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) - return 0; + // copy elements to runtime playfield array + for (x = 0; x < MAX_LEV_FIELDX; x++) + for (y = 0; y < MAX_LEV_FIELDY; y++) + Tile[x][y] = level->field[x][y]; - if (write_element) - num_bytes += putFile16BitBE(file, element); + // initialize level size variables for faster access + lev_fieldx = level->fieldx; + lev_fieldy = level->fieldy; - num_bytes += putFile8Bit(file, conf_type); - num_bytes += putFile16BitBE(file, num_contents * CONF_CONTENT_NUM_BYTES); + // determine border element for this level + if (level->file_info.type == LEVEL_FILE_TYPE_DC) + BorderElement = EL_EMPTY; // (in editor, SetBorderElement() is used) + else + SetBorderElement(); +} - for (i = 0; i < num_contents; i++) - for (y = 0; y < 3; y++) - for (x = 0; x < 3; x++) - num_bytes += putFile16BitBE(file, content[i].e[x][y]); - } +static void LoadLevel_InitNativeEngines(struct LevelInfo *level) +{ + struct LevelFileInfo *level_file_info = &level->file_info; - return num_bytes; + if (level_file_info->type == LEVEL_FILE_TYPE_RND) + CopyNativeLevel_RND_to_Native(level); } -static int SaveLevel_INFO(FILE *file, struct LevelInfo *level) +static void LoadLevelTemplate_LoadAndInit(void) { - int chunk_size = 0; - int i; + LoadLevelFromFileInfo(&level_template, &level_template.file_info, FALSE); - li = *level; // copy level data into temporary buffer + LoadLevel_InitVersion(&level_template); + LoadLevel_InitElements(&level_template); + LoadLevel_InitSettings(&level_template); - for (i = 0; chunk_config_INFO[i].data_type != -1; i++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_INFO[i], FALSE); + ActivateLevelTemplate(); +} + +void LoadLevelTemplate(int nr) +{ + if (!fileExists(getGlobalLevelTemplateFilename())) + { + Warn("no level template found for this level"); + + return; + } - return chunk_size; + setLevelFileInfo(&level_template.file_info, nr); + + LoadLevelTemplate_LoadAndInit(); } -static int SaveLevel_ELEM(FILE *file, struct LevelInfo *level) +static void LoadNetworkLevelTemplate(struct NetworkLevelInfo *network_level) { - int chunk_size = 0; - int i; - - li = *level; // copy level data into temporary buffer - - for (i = 0; chunk_config_ELEM[i].data_type != -1; i++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_ELEM[i], TRUE); + copyLevelFileInfo(&network_level->tmpl_info, &level_template.file_info); - return chunk_size; + LoadLevelTemplate_LoadAndInit(); } -static int SaveLevel_NOTE(FILE *file, struct LevelInfo *level, int element) +static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level) { - int envelope_nr = element - EL_ENVELOPE_1; - int chunk_size = 0; - int i; - - chunk_size += putFile16BitBE(file, element); + LoadLevelFromFileInfo(&level, &level.file_info, FALSE); - // copy envelope data into temporary buffer - xx_envelope = level->envelope[envelope_nr]; + if (level.use_custom_template) + { + if (network_level != NULL) + LoadNetworkLevelTemplate(network_level); + else + LoadLevelTemplate(-1); + } - for (i = 0; chunk_config_NOTE[i].data_type != -1; i++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_NOTE[i], FALSE); + LoadLevel_InitVersion(&level); + LoadLevel_InitElements(&level); + LoadLevel_InitPlayfield(&level); + LoadLevel_InitSettings(&level); - return chunk_size; + LoadLevel_InitNativeEngines(&level); } -static int SaveLevel_CUSX(FILE *file, struct LevelInfo *level, int element) +void LoadLevel(int nr) { - struct ElementInfo *ei = &element_info[element]; - int chunk_size = 0; - int i, j; + SetLevelSetInfo(leveldir_current->identifier, nr); - chunk_size += putFile16BitBE(file, element); + setLevelFileInfo(&level.file_info, nr); - xx_ei = *ei; // copy element data into temporary buffer + LoadLevel_LoadAndInit(NULL); +} - // set default description string for this specific element - strcpy(xx_default_description, getDefaultElementDescription(ei)); +void LoadLevelInfoOnly(int nr) +{ + setLevelFileInfo(&level.file_info, nr); - for (i = 0; chunk_config_CUSX_base[i].data_type != -1; i++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_CUSX_base[i], FALSE); + LoadLevelFromFileInfo(&level, &level.file_info, TRUE); +} - for (i = 0; i < ei->num_change_pages; i++) - { - struct ElementChangeInfo *change = &ei->change_page[i]; +void LoadNetworkLevel(struct NetworkLevelInfo *network_level) +{ + SetLevelSetInfo(network_level->leveldir_identifier, + network_level->file_info.nr); - xx_current_change_page = i; + copyLevelFileInfo(&network_level->file_info, &level.file_info); - xx_change = *change; // copy change data into temporary buffer + LoadLevel_LoadAndInit(network_level); +} - resetEventBits(); - setEventBitsFromEventFlags(change); +static int SaveLevel_VERS(FILE *file, struct LevelInfo *level) +{ + int chunk_size = 0; - for (j = 0; chunk_config_CUSX_change[j].data_type != -1; j++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_CUSX_change[j], - FALSE); - } + chunk_size += putFileVersion(file, level->file_version); + chunk_size += putFileVersion(file, level->game_version); return chunk_size; } -static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element) +static int SaveLevel_DATE(FILE *file, struct LevelInfo *level) { - struct ElementInfo *ei = &element_info[element]; - struct ElementGroupInfo *group = ei->group; int chunk_size = 0; - int i; - - chunk_size += putFile16BitBE(file, element); - - xx_ei = *ei; // copy element data into temporary buffer - xx_group = *group; // copy group data into temporary buffer - - // set default description string for this specific element - strcpy(xx_default_description, getDefaultElementDescription(ei)); - for (i = 0; chunk_config_GRPX[i].data_type != -1; i++) - chunk_size += SaveLevel_MicroChunk(file, &chunk_config_GRPX[i], FALSE); + chunk_size += putFile16BitBE(file, level->creation_date.year); + chunk_size += putFile8Bit(file, level->creation_date.month); + chunk_size += putFile8Bit(file, level->creation_date.day); return chunk_size; } -static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, - boolean save_as_template) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level) { - int chunk_size; - int i; - FILE *file; + int i, x, y; - if (!(file = fopen(filename, MODE_WRITE))) - { - Warn("cannot save level file '%s'", filename); + putFile8Bit(file, level->fieldx); + putFile8Bit(file, level->fieldy); - return; - } + putFile16BitBE(file, level->time); + putFile16BitBE(file, level->gems_needed); - level->file_version = FILE_VERSION_ACTUAL; - level->game_version = GAME_VERSION_ACTUAL; + for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) + putFile8Bit(file, level->name[i]); - level->creation_date = getCurrentDate(); + for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) + putFile8Bit(file, level->score[i]); - putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); - putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE); + for (i = 0; i < STD_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile8Bit(file, (level->encoding_16bit_yamyam ? EL_EMPTY : + level->yamyam_content[i].e[x][y])); + putFile8Bit(file, level->amoeba_speed); + putFile8Bit(file, level->time_magic_wall); + putFile8Bit(file, level->time_wheel); + putFile8Bit(file, (level->encoding_16bit_amoeba ? EL_EMPTY : + level->amoeba_content)); + putFile8Bit(file, (level->initial_player_stepsize == STEPSIZE_FAST ? 1 : 0)); + putFile8Bit(file, (level->initial_gravity ? 1 : 0)); + putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0)); + putFile8Bit(file, (level->em_slippery_gems ? 1 : 0)); - chunk_size = SaveLevel_VERS(NULL, level); - putFileChunkBE(file, "VERS", chunk_size); - SaveLevel_VERS(file, level); + putFile8Bit(file, (level->use_custom_template ? 1 : 0)); - chunk_size = SaveLevel_DATE(NULL, level); - putFileChunkBE(file, "DATE", chunk_size); - SaveLevel_DATE(file, 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); - chunk_size = SaveLevel_NAME(NULL, level); - putFileChunkBE(file, "NAME", chunk_size); - SaveLevel_NAME(file, level); + putFile8Bit(file, (level->use_spring_bug ? 1 : 0)); + putFile8Bit(file, (level->use_step_counter ? 1 : 0)); - chunk_size = SaveLevel_AUTH(NULL, level); - putFileChunkBE(file, "AUTH", chunk_size); - SaveLevel_AUTH(file, level); + putFile8Bit(file, (level->instant_relocation ? 1 : 0)); + putFile8Bit(file, (level->can_pass_to_walkable ? 1 : 0)); + putFile8Bit(file, (level->grow_into_diggable ? 1 : 0)); - chunk_size = SaveLevel_INFO(NULL, level); - putFileChunkBE(file, "INFO", chunk_size); - SaveLevel_INFO(file, level); + putFile8Bit(file, level->game_engine_type); - chunk_size = SaveLevel_BODY(NULL, level); - putFileChunkBE(file, "BODY", chunk_size); - SaveLevel_BODY(file, level); + WriteUnusedBytesToFile(file, LEVEL_CHUNK_HEAD_UNUSED); +} +#endif - chunk_size = SaveLevel_ELEM(NULL, level); - if (chunk_size > LEVEL_CHUNK_ELEM_UNCHANGED) // save if changed - { - putFileChunkBE(file, "ELEM", chunk_size); - SaveLevel_ELEM(file, level); - } +static int SaveLevel_NAME(FILE *file, struct LevelInfo *level) +{ + int chunk_size = 0; + int i; - for (i = 0; i < NUM_ENVELOPES; i++) - { - int element = EL_ENVELOPE_1 + i; + for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) + chunk_size += putFile8Bit(file, level->name[i]); - chunk_size = SaveLevel_NOTE(NULL, level, element); - if (chunk_size > LEVEL_CHUNK_NOTE_UNCHANGED) // save if changed - { - putFileChunkBE(file, "NOTE", chunk_size); - SaveLevel_NOTE(file, level, element); - } - } + return chunk_size; +} - // if not using template level, check for non-default custom/group elements - if (!level->use_custom_template || save_as_template) - { - for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) - { - int element = EL_CUSTOM_START + i; +static int SaveLevel_AUTH(FILE *file, struct LevelInfo *level) +{ + int chunk_size = 0; + int i; - chunk_size = SaveLevel_CUSX(NULL, level, element); - if (chunk_size > LEVEL_CHUNK_CUSX_UNCHANGED) // save if changed - { - putFileChunkBE(file, "CUSX", chunk_size); - SaveLevel_CUSX(file, level, element); - } - } + for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++) + chunk_size += putFile8Bit(file, level->author[i]); - for (i = 0; i < NUM_GROUP_ELEMENTS; i++) - { - int element = EL_GROUP_START + i; + return chunk_size; +} - chunk_size = SaveLevel_GRPX(NULL, level, element); - if (chunk_size > LEVEL_CHUNK_GRPX_UNCHANGED) // save if changed - { - putFileChunkBE(file, "GRPX", chunk_size); - SaveLevel_GRPX(file, level, element); - } - } - } +#if ENABLE_HISTORIC_CHUNKS +static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) +{ + int chunk_size = 0; + int x, y; - fclose(file); + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + if (level->encoding_16bit_field) + chunk_size += putFile16BitBE(file, level->field[x][y]); + else + chunk_size += putFile8Bit(file, level->field[x][y]); - SetFilePermissions(filename, PERMS_PRIVATE); + return chunk_size; } +#endif -void SaveLevel(int nr) +static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) { - char *filename = getDefaultLevelFilename(nr); + int chunk_size = 0; + int x, y; - SaveLevelFromFilename(&level, filename, FALSE); + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + chunk_size += putFile16BitBE(file, level->field[x][y]); + + return chunk_size; } -void SaveLevelTemplate(void) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CONT(FILE *file, struct LevelInfo *level) { - char *filename = getLocalLevelTemplateFilename(); + int i, x, y; - SaveLevelFromFilename(&level, filename, TRUE); + putFile8Bit(file, EL_YAMYAM); + putFile8Bit(file, level->num_yamyam_contents); + putFile8Bit(file, 0); + putFile8Bit(file, 0); + + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + if (level->encoding_16bit_field) + putFile16BitBE(file, level->yamyam_content[i].e[x][y]); + else + putFile8Bit(file, level->yamyam_content[i].e[x][y]); } +#endif -boolean SaveLevelChecked(int nr) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) { - char *filename = getDefaultLevelFilename(nr); - boolean new_level = !fileExists(filename); - boolean level_saved = FALSE; + int i, x, y; + int num_contents, content_xsize, content_ysize; + int content_array[MAX_ELEMENT_CONTENTS][3][3]; - if (new_level || Request("Save this level and kill the old?", REQ_ASK)) + if (element == EL_YAMYAM) { - SaveLevel(nr); - - if (new_level) - Request("Level saved!", REQ_CONFIRM); + num_contents = level->num_yamyam_contents; + content_xsize = 3; + content_ysize = 3; - level_saved = TRUE; + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content_array[i][x][y] = level->yamyam_content[i].e[x][y]; } + else if (element == EL_BD_AMOEBA) + { + num_contents = 1; + content_xsize = 1; + content_ysize = 1; - return level_saved; -} - -void DumpLevel(struct LevelInfo *level) -{ - if (level->no_level_file || level->no_valid_file) + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + content_array[i][x][y] = EL_EMPTY; + content_array[0][0][0] = level->amoeba_content; + } + else { - Warn("cannot dump -- no valid level file found"); + // chunk header already written -- write empty chunk data + WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE); + + Warn("cannot save content for element '%d'", element); return; } - PrintLine("-", 79); - Print("Level xxx (file version %08d, game version %08d)\n", - level->file_version, level->game_version); - PrintLine("-", 79); - - Print("Level author: '%s'\n", level->author); - Print("Level title: '%s'\n", level->name); - Print("\n"); - Print("Playfield size: %d x %d\n", level->fieldx, level->fieldy); - Print("\n"); - Print("Level time: %d seconds\n", level->time); - Print("Gems needed: %d\n", level->gems_needed); - Print("\n"); - Print("Time for magic wall: %d seconds\n", level->time_magic_wall); - Print("Time for wheel: %d seconds\n", level->time_wheel); - Print("Time for light: %d seconds\n", level->time_light); - Print("Time for timegate: %d seconds\n", level->time_timegate); - Print("\n"); - Print("Amoeba speed: %d\n", level->amoeba_speed); - Print("\n"); + putFile16BitBE(file, element); + putFile8Bit(file, num_contents); + putFile8Bit(file, content_xsize); + putFile8Bit(file, content_ysize); - Print("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no")); - Print("Player blocks last field: %s\n", (level->block_last_field ? "yes" : "no")); - Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no")); - Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no")); - Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no")); - Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no")); + WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_UNUSED); - PrintLine("-", 79); + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile16BitBE(file, content_array[i][x][y]); } +#endif -void DumpLevels(void) +#if ENABLE_HISTORIC_CHUNKS +static int SaveLevel_CNT3(FILE *file, struct LevelInfo *level, int element) { - static LevelDirTree *dumplevel_leveldir = NULL; - - dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first, - global.dumplevel_leveldir); - - if (dumplevel_leveldir == NULL) - Fail("no such level identifier: '%s'", global.dumplevel_leveldir); + int envelope_nr = element - EL_ENVELOPE_1; + int envelope_len = strlen(level->envelope_text[envelope_nr]) + 1; + int chunk_size = 0; + int i; - if (global.dumplevel_level_nr < dumplevel_leveldir->first_level || - global.dumplevel_level_nr > dumplevel_leveldir->last_level) - Fail("no such level number: %d", global.dumplevel_level_nr); + chunk_size += putFile16BitBE(file, element); + chunk_size += putFile16BitBE(file, envelope_len); + chunk_size += putFile8Bit(file, level->envelope_xsize[envelope_nr]); + chunk_size += putFile8Bit(file, level->envelope_ysize[envelope_nr]); - leveldir_current = dumplevel_leveldir; + WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT3_UNUSED); + chunk_size += LEVEL_CHUNK_CNT3_UNUSED; - LoadLevel(global.dumplevel_level_nr); - DumpLevel(&level); + for (i = 0; i < envelope_len; i++) + chunk_size += putFile8Bit(file, level->envelope_text[envelope_nr][i]); - CloseAllAndExit(0); + return chunk_size; } +#endif - -// ============================================================================ -// tape file functions -// ============================================================================ - -static void setTapeInfoToDefaults(void) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) { - int i; - - // always start with reliable default values (empty tape) - TapeErase(); - - // default values (also for pre-1.2 tapes) with only the first player - tape.player_participates[0] = TRUE; - for (i = 1; i < MAX_PLAYERS; i++) - tape.player_participates[i] = FALSE; - - // at least one (default: the first) player participates in every tape - tape.num_participating_players = 1; - - tape.property_bits = TAPE_PROPERTY_NONE; - - tape.level_nr = level_nr; - tape.counter = 0; - tape.changed = FALSE; - - tape.recording = FALSE; - tape.playing = FALSE; - tape.pausing = FALSE; - - tape.scr_fieldx = SCR_FIELDX_DEFAULT; - tape.scr_fieldy = SCR_FIELDY_DEFAULT; + int i, check = 0; - tape.no_info_chunk = TRUE; - tape.no_valid_file = FALSE; -} + putFile16BitBE(file, num_changed_custom_elements); -static int getTapePosSize(struct TapeInfo *tape) -{ - int tape_pos_size = 0; + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; - if (tape->use_key_actions) - tape_pos_size += tape->num_participating_players; + struct ElementInfo *ei = &element_info[element]; - if (tape->use_mouse_actions) - tape_pos_size += 3; // x and y position and mouse button mask + if (ei->properties[EP_BITFIELD_BASE_NR] != EP_BITMASK_DEFAULT) + { + if (check < num_changed_custom_elements) + { + putFile16BitBE(file, element); + putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); + } - tape_pos_size += 1; // tape action delay value + check++; + } + } - return tape_pos_size; + if (check != num_changed_custom_elements) // should not happen + Warn("inconsistent number of custom element properties"); } +#endif -static void setTapeActionFlags(struct TapeInfo *tape, int value) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) { - tape->use_key_actions = FALSE; - tape->use_mouse_actions = FALSE; + int i, check = 0; - if (value != TAPE_USE_MOUSE_ACTIONS_ONLY) - tape->use_key_actions = TRUE; + putFile16BitBE(file, num_changed_custom_elements); - if (value != TAPE_USE_KEY_ACTIONS_ONLY) - tape->use_mouse_actions = TRUE; -} + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; -static int getTapeActionValue(struct TapeInfo *tape) -{ - return (tape->use_key_actions && - tape->use_mouse_actions ? TAPE_USE_KEY_AND_MOUSE_ACTIONS : - tape->use_key_actions ? TAPE_USE_KEY_ACTIONS_ONLY : - tape->use_mouse_actions ? TAPE_USE_MOUSE_ACTIONS_ONLY : - TAPE_ACTIONS_DEFAULT); -} + if (element_info[element].change->target_element != EL_EMPTY_SPACE) + { + if (check < num_changed_custom_elements) + { + putFile16BitBE(file, element); + putFile16BitBE(file, element_info[element].change->target_element); + } -static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape) -{ - tape->file_version = getFileVersion(file); - tape->game_version = getFileVersion(file); + check++; + } + } - return chunk_size; + if (check != num_changed_custom_elements) // should not happen + Warn("inconsistent number of custom target elements"); } +#endif -static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) { - int i; + int i, j, x, y, check = 0; - tape->random_seed = getFile32BitBE(file); - tape->date = getFile32BitBE(file); - tape->length = getFile32BitBE(file); + putFile16BitBE(file, num_changed_custom_elements); - // read header fields that are new since version 1.2 - if (tape->file_version >= FILE_VERSION_1_2) + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) { - byte store_participating_players = getFile8Bit(file); - int engine_version; + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; - // since version 1.2, tapes store which players participate in the tape - tape->num_participating_players = 0; - for (i = 0; i < MAX_PLAYERS; i++) + if (ei->modified_settings) { - tape->player_participates[i] = FALSE; - - if (store_participating_players & (1 << i)) + if (check < num_changed_custom_elements) { - tape->player_participates[i] = TRUE; - tape->num_participating_players++; - } - } - - setTapeActionFlags(tape, getFile8Bit(file)); + putFile16BitBE(file, element); - tape->property_bits = getFile8Bit(file); + for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++) + putFile8Bit(file, ei->description[j]); - ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED); + putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); - engine_version = getFileVersion(file); - if (engine_version > 0) - tape->engine_version = engine_version; - else - tape->engine_version = tape->game_version; - } + // some free bytes for future properties and padding + WriteUnusedBytesToFile(file, 7); - return chunk_size; -} + putFile8Bit(file, ei->use_gfx_element); + putFile16BitBE(file, ei->gfx_element_initial); -static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape) -{ - tape->scr_fieldx = getFile8Bit(file); - tape->scr_fieldy = getFile8Bit(file); + putFile8Bit(file, ei->collect_score_initial); + putFile8Bit(file, ei->collect_count_initial); - return chunk_size; -} + putFile16BitBE(file, ei->push_delay_fixed); + putFile16BitBE(file, ei->push_delay_random); + putFile16BitBE(file, ei->move_delay_fixed); + putFile16BitBE(file, ei->move_delay_random); -static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) -{ - char *level_identifier = NULL; - int level_identifier_size; - int i; + putFile16BitBE(file, ei->move_pattern); + putFile8Bit(file, ei->move_direction_initial); + putFile8Bit(file, ei->move_stepsize); - tape->no_info_chunk = FALSE; + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile16BitBE(file, ei->content.e[x][y]); - level_identifier_size = getFile16BitBE(file); + putFile32BitBE(file, ei->change->events); - level_identifier = checked_malloc(level_identifier_size); + putFile16BitBE(file, ei->change->target_element); - for (i = 0; i < level_identifier_size; i++) - level_identifier[i] = getFile8Bit(file); + putFile16BitBE(file, ei->change->delay_fixed); + putFile16BitBE(file, ei->change->delay_random); + putFile16BitBE(file, ei->change->delay_frames); - strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN); - tape->level_identifier[MAX_FILENAME_LEN] = '\0'; + putFile16BitBE(file, ei->change->initial_trigger_element); - checked_free(level_identifier); + putFile8Bit(file, ei->change->explode); + putFile8Bit(file, ei->change->use_target_content); + putFile8Bit(file, ei->change->only_if_complete); + putFile8Bit(file, ei->change->use_random_replace); - tape->level_nr = getFile16BitBE(file); + putFile8Bit(file, ei->change->random_percentage); + putFile8Bit(file, ei->change->replace_when); - chunk_size = 2 + level_identifier_size + 2; + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile16BitBE(file, ei->change->content.e[x][y]); - return chunk_size; -} + putFile8Bit(file, ei->slippery_type); -static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) -{ - int i, j; - int tape_pos_size = getTapePosSize(tape); - int chunk_size_expected = tape_pos_size * tape->length; + // some free bytes for future properties and padding + WriteUnusedBytesToFile(file, LEVEL_CPART_CUS3_UNUSED); + } - if (chunk_size_expected != chunk_size) - { - ReadUnusedBytesFromFile(file, chunk_size); - return chunk_size_expected; + check++; + } } - for (i = 0; i < tape->length; i++) - { - if (i >= MAX_TAPE_LEN) - { - Warn("tape truncated -- size exceeds maximum tape size %d", - MAX_TAPE_LEN); + if (check != num_changed_custom_elements) // should not happen + Warn("inconsistent number of custom element properties"); +} +#endif - // tape too large; read and ignore remaining tape data from this chunk - for (;i < tape->length; i++) - ReadUnusedBytesFromFile(file, tape_pos_size); +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) +{ + struct ElementInfo *ei = &element_info[element]; + int i, j, x, y; - break; - } + // ---------- custom element base property values (96 bytes) ---------------- - if (tape->use_key_actions) - { - for (j = 0; j < MAX_PLAYERS; j++) - { - tape->pos[i].action[j] = MV_NONE; + putFile16BitBE(file, element); - if (tape->player_participates[j]) - tape->pos[i].action[j] = getFile8Bit(file); - } - } + for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) + putFile8Bit(file, ei->description[i]); - if (tape->use_mouse_actions) - { - tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file); - tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file); - tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file); - } + putFile32BitBE(file, ei->properties[EP_BITFIELD_BASE_NR]); - tape->pos[i].delay = getFile8Bit(file); + WriteUnusedBytesToFile(file, 4); // reserved for more base properties - if (tape->file_version == FILE_VERSION_1_0) - { - // eliminate possible diagonal moves in old tapes - // this is only for backward compatibility + putFile8Bit(file, ei->num_change_pages); - byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN }; - byte action = tape->pos[i].action[0]; - int k, num_moves = 0; + putFile16BitBE(file, ei->ce_value_fixed_initial); + putFile16BitBE(file, ei->ce_value_random_initial); + putFile8Bit(file, ei->use_last_ce_value); - for (k = 0; k<4; k++) - { - if (action & joy_dir[k]) - { - tape->pos[i + num_moves].action[0] = joy_dir[k]; - if (num_moves > 0) - tape->pos[i + num_moves].delay = 0; - num_moves++; - } - } + putFile8Bit(file, ei->use_gfx_element); + putFile16BitBE(file, ei->gfx_element_initial); - if (num_moves > 1) - { - num_moves--; - i += num_moves; - tape->length += num_moves; - } - } - else if (tape->file_version < FILE_VERSION_2_0) - { - // convert pre-2.0 tapes to new tape format + putFile8Bit(file, ei->collect_score_initial); + putFile8Bit(file, ei->collect_count_initial); - if (tape->pos[i].delay > 1) - { - // action part - tape->pos[i + 1] = tape->pos[i]; - tape->pos[i + 1].delay = 1; + 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); - // delay part - for (j = 0; j < MAX_PLAYERS; j++) - tape->pos[i].action[j] = MV_NONE; - tape->pos[i].delay--; + // bits 0 - 15 of "move_pattern" ... + putFile16BitBE(file, ei->move_pattern & 0xffff); + putFile8Bit(file, ei->move_direction_initial); + putFile8Bit(file, ei->move_stepsize); - i++; - tape->length++; - } - } + putFile8Bit(file, ei->slippery_type); - if (checkEndOfFile(file)) - break; - } + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile16BitBE(file, ei->content.e[x][y]); - if (i != tape->length) - chunk_size = tape_pos_size * i; + putFile16BitBE(file, ei->move_enter_element); + putFile16BitBE(file, ei->move_leave_element); + putFile8Bit(file, ei->move_leave_type); - return chunk_size; -} + // ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) + putFile16BitBE(file, (ei->move_pattern >> 16) & 0xffff); -static void LoadTape_SokobanSolution(char *filename) -{ - File *file; - int move_delay = TILESIZE / level.initial_player_stepsize[0]; + putFile8Bit(file, ei->access_direction); - if (!(file = openFile(filename, MODE_READ))) - { - tape.no_valid_file = TRUE; + putFile8Bit(file, ei->explosion_delay); + putFile8Bit(file, ei->ignition_delay); + putFile8Bit(file, ei->explosion_type); - return; - } + // some free bytes for future custom property values and padding + WriteUnusedBytesToFile(file, 1); - while (!checkEndOfFile(file)) + // ---------- change page property values (48 bytes) ------------------------ + + for (i = 0; i < ei->num_change_pages; i++) { - unsigned char c = getByteFromFile(file); + struct ElementChangeInfo *change = &ei->change_page[i]; + unsigned int event_bits; - if (checkEndOfFile(file)) - break; + // bits 0 - 31 of "has_event[]" ... + event_bits = 0; + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) + if (change->has_event[j]) + event_bits |= (1u << j); + putFile32BitBE(file, event_bits); - switch (c) - { - case 'u': - case 'U': - tape.pos[tape.length].action[0] = MV_UP; - tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); - tape.length++; - break; + putFile16BitBE(file, change->target_element); - case 'd': - case 'D': - tape.pos[tape.length].action[0] = MV_DOWN; - tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); - tape.length++; - break; + putFile16BitBE(file, change->delay_fixed); + putFile16BitBE(file, change->delay_random); + putFile16BitBE(file, change->delay_frames); - case 'l': - case 'L': - tape.pos[tape.length].action[0] = MV_LEFT; - tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); - tape.length++; - break; + putFile16BitBE(file, change->initial_trigger_element); - case 'r': - case 'R': - tape.pos[tape.length].action[0] = MV_RIGHT; - tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); - tape.length++; - break; + putFile8Bit(file, change->explode); + putFile8Bit(file, change->use_target_content); + putFile8Bit(file, change->only_if_complete); + putFile8Bit(file, change->use_random_replace); - case '\n': - case '\r': - case '\t': - case ' ': - // ignore white-space characters - break; + putFile8Bit(file, change->random_percentage); + putFile8Bit(file, change->replace_when); - default: - tape.no_valid_file = TRUE; + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + putFile16BitBE(file, change->target_content.e[x][y]); - Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c); + putFile8Bit(file, change->can_change); - break; - } - } + putFile8Bit(file, change->trigger_side); - closeFile(file); + putFile8Bit(file, change->trigger_player); + putFile8Bit(file, (change->trigger_page == CH_PAGE_ANY ? CH_PAGE_ANY_FILE : + log_2(change->trigger_page))); - if (tape.no_valid_file) - return; + putFile8Bit(file, change->has_action); + putFile8Bit(file, change->action_type); + putFile8Bit(file, change->action_mode); + putFile16BitBE(file, change->action_arg); - tape.length_frames = GetTapeLengthFrames(); - tape.length_seconds = GetTapeLengthSeconds(); + // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) + event_bits = 0; + for (j = 32; j < NUM_CHANGE_EVENTS; j++) + if (change->has_event[j]) + event_bits |= (1u << (j - 32)); + putFile8Bit(file, event_bits); + } } +#endif -void LoadTapeFromFilename(char *filename) +#if ENABLE_HISTORIC_CHUNKS +static void SaveLevel_GRP1(FILE *file, struct LevelInfo *level, int element) { - char cookie[MAX_LINE_LEN]; - char chunk_name[CHUNK_ID_LEN + 1]; - File *file; - int chunk_size; + struct ElementInfo *ei = &element_info[element]; + struct ElementGroupInfo *group = ei->group; + int i; - // always start with reliable default values - setTapeInfoToDefaults(); + putFile16BitBE(file, element); - if (strSuffix(filename, ".sln")) - { - LoadTape_SokobanSolution(filename); + for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++) + putFile8Bit(file, ei->description[i]); - return; - } + putFile8Bit(file, group->num_elements); - if (!(file = openFile(filename, MODE_READ))) - { - tape.no_valid_file = TRUE; + putFile8Bit(file, ei->use_gfx_element); + putFile16BitBE(file, ei->gfx_element_initial); - return; - } + putFile8Bit(file, group->choice_mode); - getFileChunkBE(file, chunk_name, NULL); - if (strEqual(chunk_name, "RND1")) + // some free bytes for future values and padding + WriteUnusedBytesToFile(file, 3); + + for (i = 0; i < MAX_ELEMENTS_IN_GROUP; i++) + putFile16BitBE(file, group->element[i]); +} +#endif + +static int SaveLevel_MicroChunk(FILE *file, struct LevelFileConfigInfo *entry, + boolean write_element) +{ + int save_type = entry->save_type; + int data_type = entry->data_type; + int conf_type = entry->conf_type; + int byte_mask = conf_type & CONF_MASK_BYTES; + int element = entry->element; + int default_value = entry->default_value; + int num_bytes = 0; + boolean modified = FALSE; + + if (byte_mask != CONF_MASK_MULTI_BYTES) { - getFile32BitBE(file); // not used + void *value_ptr = entry->value; + int value = (data_type == TYPE_BOOLEAN ? *(boolean *)value_ptr : + *(int *)value_ptr); - getFileChunkBE(file, chunk_name, NULL); - if (!strEqual(chunk_name, "TAPE")) - { - tape.no_valid_file = TRUE; + // check if any settings have been modified before saving them + if (value != default_value) + modified = TRUE; - Warn("unknown format of tape file '%s'", filename); + // do not save if explicitly told or if unmodified default settings + if ((save_type == SAVE_CONF_NEVER) || + (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) + return 0; - closeFile(file); + if (write_element) + num_bytes += putFile16BitBE(file, element); - return; - } + num_bytes += putFile8Bit(file, conf_type); + num_bytes += (byte_mask == CONF_MASK_1_BYTE ? putFile8Bit (file, value) : + byte_mask == CONF_MASK_2_BYTE ? putFile16BitBE(file, value) : + byte_mask == CONF_MASK_4_BYTE ? putFile32BitBE(file, value) : + 0); } - else // check for pre-2.0 file format with cookie string + else if (data_type == TYPE_STRING) { - strcpy(cookie, chunk_name); - if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) - cookie[4] = '\0'; - if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') - cookie[strlen(cookie) - 1] = '\0'; + char *default_string = entry->default_string; + char *string = (char *)(entry->value); + int string_length = strlen(string); + int i; - if (!checkCookieString(cookie, TAPE_COOKIE_TMPL)) - { - tape.no_valid_file = TRUE; + // check if any settings have been modified before saving them + if (!strEqual(string, default_string)) + modified = TRUE; - Warn("unknown format of tape file '%s'", filename); + // do not save if explicitly told or if unmodified default settings + if ((save_type == SAVE_CONF_NEVER) || + (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) + return 0; - closeFile(file); + if (write_element) + num_bytes += putFile16BitBE(file, element); - return; - } + num_bytes += putFile8Bit(file, conf_type); + num_bytes += putFile16BitBE(file, string_length); - if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1) - { - tape.no_valid_file = TRUE; + for (i = 0; i < string_length; i++) + num_bytes += putFile8Bit(file, string[i]); + } + else if (data_type == TYPE_ELEMENT_LIST) + { + int *element_array = (int *)(entry->value); + int num_elements = *(int *)(entry->num_entities); + int i; - Warn("unsupported version of tape file '%s'", filename); + // check if any settings have been modified before saving them + for (i = 0; i < num_elements; i++) + if (element_array[i] != default_value) + modified = TRUE; - closeFile(file); + // do not save if explicitly told or if unmodified default settings + if ((save_type == SAVE_CONF_NEVER) || + (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) + return 0; - return; - } + if (write_element) + num_bytes += putFile16BitBE(file, element); - // pre-2.0 tape files have no game version, so use file version here - tape.game_version = tape.file_version; - } + num_bytes += putFile8Bit(file, conf_type); + num_bytes += putFile16BitBE(file, num_elements * CONF_ELEMENT_NUM_BYTES); - if (tape.file_version < FILE_VERSION_1_2) - { - // tape files from versions before 1.2.0 without chunk structure - LoadTape_HEAD(file, TAPE_CHUNK_HEAD_SIZE, &tape); - LoadTape_BODY(file, 2 * tape.length, &tape); + for (i = 0; i < num_elements; i++) + num_bytes += putFile16BitBE(file, element_array[i]); } - else + else if (data_type == TYPE_CONTENT_LIST) { - static struct - { - char *name; - int size; - int (*loader)(File *, int, struct TapeInfo *); - } - chunk_info[] = - { - { "VERS", TAPE_CHUNK_VERS_SIZE, LoadTape_VERS }, - { "HEAD", TAPE_CHUNK_HEAD_SIZE, LoadTape_HEAD }, - { "SCRN", TAPE_CHUNK_SCRN_SIZE, LoadTape_SCRN }, - { "INFO", -1, LoadTape_INFO }, - { "BODY", -1, LoadTape_BODY }, - { NULL, 0, NULL } - }; - - while (getFileChunkBE(file, chunk_name, &chunk_size)) - { - int i = 0; - - while (chunk_info[i].name != NULL && - !strEqual(chunk_name, chunk_info[i].name)) - i++; - - if (chunk_info[i].name == NULL) - { - Warn("unknown chunk '%s' in tape file '%s'", - chunk_name, filename); + struct Content *content = (struct Content *)(entry->value); + int num_contents = *(int *)(entry->num_entities); + int i, x, y; - ReadUnusedBytesFromFile(file, chunk_size); - } - else if (chunk_info[i].size != -1 && - chunk_info[i].size != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in tape file '%s'", - chunk_size, chunk_name, filename); + // check if any settings have been modified before saving them + for (i = 0; i < num_contents; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + if (content[i].e[x][y] != default_value) + modified = TRUE; - ReadUnusedBytesFromFile(file, chunk_size); - } - else - { - // call function to load this tape chunk - int chunk_size_expected = - (chunk_info[i].loader)(file, chunk_size, &tape); + // do not save if explicitly told or if unmodified default settings + if ((save_type == SAVE_CONF_NEVER) || + (save_type == SAVE_CONF_WHEN_CHANGED && !modified)) + return 0; - // the size of some chunks cannot be checked before reading other - // chunks first (like "HEAD" and "BODY") that contain some header - // information, so check them here - if (chunk_size_expected != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in tape file '%s'", - chunk_size, chunk_name, filename); - } - } - } - } + if (write_element) + num_bytes += putFile16BitBE(file, element); - closeFile(file); + num_bytes += putFile8Bit(file, conf_type); + num_bytes += putFile16BitBE(file, num_contents * CONF_CONTENT_NUM_BYTES); - tape.length_frames = GetTapeLengthFrames(); - tape.length_seconds = GetTapeLengthSeconds(); + for (i = 0; i < num_contents; i++) + for (y = 0; y < 3; y++) + for (x = 0; x < 3; x++) + num_bytes += putFile16BitBE(file, content[i].e[x][y]); + } -#if 0 - Debug("files:LoadTapeFromFilename", "tape file version: %d", - tape.file_version); - Debug("files:LoadTapeFromFilename", "tape game version: %d", - tape.game_version); - Debug("files:LoadTapeFromFilename", "tape engine version: %d", - tape.engine_version); -#endif + return num_bytes; } -void LoadTape(int nr) +static int SaveLevel_INFO(FILE *file, struct LevelInfo *level) { - char *filename = getTapeFilename(nr); + int chunk_size = 0; + int i; - LoadTapeFromFilename(filename); + li = *level; // copy level data into temporary buffer + + for (i = 0; chunk_config_INFO[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_INFO[i], FALSE); + + return chunk_size; } -void LoadSolutionTape(int nr) +static int SaveLevel_ELEM(FILE *file, struct LevelInfo *level) { - char *filename = getSolutionTapeFilename(nr); + int chunk_size = 0; + int i; - LoadTapeFromFilename(filename); + li = *level; // copy level data into temporary buffer - if (TAPE_IS_EMPTY(tape) && - level.game_engine_type == GAME_ENGINE_TYPE_SP && - level.native_sp_level->demo.is_available) - CopyNativeTape_SP_to_RND(&level); -} + for (i = 0; chunk_config_ELEM[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_ELEM[i], TRUE); -static boolean checkSaveTape_SCRN(struct TapeInfo *tape) -{ - // chunk required for team mode tapes with non-default screen size - return (tape->num_participating_players > 1 && - (tape->scr_fieldx != SCR_FIELDX_DEFAULT || - tape->scr_fieldy != SCR_FIELDY_DEFAULT)); + return chunk_size; } -static void SaveTape_VERS(FILE *file, struct TapeInfo *tape) +static int SaveLevel_NOTE(FILE *file, struct LevelInfo *level, int element) { - putFileVersion(file, tape->file_version); - putFileVersion(file, tape->game_version); + int envelope_nr = element - EL_ENVELOPE_1; + int chunk_size = 0; + int i; + + chunk_size += putFile16BitBE(file, element); + + // copy envelope data into temporary buffer + xx_envelope = level->envelope[envelope_nr]; + + for (i = 0; chunk_config_NOTE[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_NOTE[i], FALSE); + + return chunk_size; } -static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) +static int SaveLevel_CUSX(FILE *file, struct LevelInfo *level, int element) { - int i; - byte store_participating_players = 0; + struct ElementInfo *ei = &element_info[element]; + int chunk_size = 0; + int i, j; - // set bits for participating players for compact storage - for (i = 0; i < MAX_PLAYERS; i++) - if (tape->player_participates[i]) - store_participating_players |= (1 << i); + chunk_size += putFile16BitBE(file, element); - putFile32BitBE(file, tape->random_seed); - putFile32BitBE(file, tape->date); - putFile32BitBE(file, tape->length); + xx_ei = *ei; // copy element data into temporary buffer - putFile8Bit(file, store_participating_players); + // set default description string for this specific element + strcpy(xx_default_description, getDefaultElementDescription(ei)); - putFile8Bit(file, getTapeActionValue(tape)); + for (i = 0; chunk_config_CUSX_base[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_CUSX_base[i], FALSE); - putFile8Bit(file, tape->property_bits); + for (i = 0; i < ei->num_change_pages; i++) + { + struct ElementChangeInfo *change = &ei->change_page[i]; - // unused bytes not at the end here for 4-byte alignment of engine_version - WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED); + xx_current_change_page = i; - putFileVersion(file, tape->engine_version); -} + xx_change = *change; // copy change data into temporary buffer -static void SaveTape_SCRN(FILE *file, struct TapeInfo *tape) -{ - putFile8Bit(file, tape->scr_fieldx); - putFile8Bit(file, tape->scr_fieldy); + resetEventBits(); + setEventBitsFromEventFlags(change); + + for (j = 0; chunk_config_CUSX_change[j].data_type != -1; j++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_CUSX_change[j], + FALSE); + } + + return chunk_size; } -static void SaveTape_INFO(FILE *file, struct TapeInfo *tape) +static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element) { - int level_identifier_size = strlen(tape->level_identifier) + 1; + struct ElementInfo *ei = &element_info[element]; + struct ElementGroupInfo *group = ei->group; + int chunk_size = 0; int i; - putFile16BitBE(file, level_identifier_size); + chunk_size += putFile16BitBE(file, element); - for (i = 0; i < level_identifier_size; i++) - putFile8Bit(file, tape->level_identifier[i]); + xx_ei = *ei; // copy element data into temporary buffer + xx_group = *group; // copy group data into temporary buffer - putFile16BitBE(file, tape->level_nr); + // set default description string for this specific element + strcpy(xx_default_description, getDefaultElementDescription(ei)); + + for (i = 0; chunk_config_GRPX[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_GRPX[i], FALSE); + + return chunk_size; } -static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) +static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element) { - int i, j; + struct ElementInfo *ei = &element_info[element]; + int chunk_size = 0; + int i; - for (i = 0; i < tape->length; i++) - { - if (tape->use_key_actions) - { - for (j = 0; j < MAX_PLAYERS; j++) - if (tape->player_participates[j]) - putFile8Bit(file, tape->pos[i].action[j]); - } + chunk_size += putFile16BitBE(file, element); - if (tape->use_mouse_actions) - { - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]); - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]); - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]); - } + xx_ei = *ei; // copy element data into temporary buffer - putFile8Bit(file, tape->pos[i].delay); - } + for (i = 0; chunk_config_EMPX[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE); + + return chunk_size; } -void SaveTapeToFilename(char *filename) +static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, + boolean save_as_template) { + int chunk_size; + int i; FILE *file; - int tape_pos_size; - int info_chunk_size; - int body_chunk_size; if (!(file = fopen(filename, MODE_WRITE))) { - Warn("cannot save level recording file '%s'", filename); + Warn("cannot save level file '%s'", filename); return; } - tape_pos_size = getTapePosSize(&tape); + level->file_version = FILE_VERSION_ACTUAL; + level->game_version = GAME_VERSION_ACTUAL; - info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2; - body_chunk_size = tape_pos_size * tape.length; + level->creation_date = getCurrentDate(); putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); - putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE); + putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE); - putFileChunkBE(file, "VERS", TAPE_CHUNK_VERS_SIZE); - SaveTape_VERS(file, &tape); + chunk_size = SaveLevel_VERS(NULL, level); + putFileChunkBE(file, "VERS", chunk_size); + SaveLevel_VERS(file, level); + + chunk_size = SaveLevel_DATE(NULL, level); + putFileChunkBE(file, "DATE", chunk_size); + SaveLevel_DATE(file, level); + + chunk_size = SaveLevel_NAME(NULL, level); + putFileChunkBE(file, "NAME", chunk_size); + SaveLevel_NAME(file, level); + + chunk_size = SaveLevel_AUTH(NULL, level); + putFileChunkBE(file, "AUTH", chunk_size); + SaveLevel_AUTH(file, level); + + chunk_size = SaveLevel_INFO(NULL, level); + putFileChunkBE(file, "INFO", chunk_size); + SaveLevel_INFO(file, level); + + chunk_size = SaveLevel_BODY(NULL, level); + putFileChunkBE(file, "BODY", chunk_size); + SaveLevel_BODY(file, level); + + chunk_size = SaveLevel_ELEM(NULL, level); + if (chunk_size > LEVEL_CHUNK_ELEM_UNCHANGED) // save if changed + { + putFileChunkBE(file, "ELEM", chunk_size); + SaveLevel_ELEM(file, level); + } + + for (i = 0; i < NUM_ENVELOPES; i++) + { + int element = EL_ENVELOPE_1 + i; + + chunk_size = SaveLevel_NOTE(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_NOTE_UNCHANGED) // save if changed + { + putFileChunkBE(file, "NOTE", chunk_size); + SaveLevel_NOTE(file, level, element); + } + } + + // if not using template level, check for non-default custom/group elements + if (!level->use_custom_template || save_as_template) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + chunk_size = SaveLevel_CUSX(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_CUSX_UNCHANGED) // save if changed + { + putFileChunkBE(file, "CUSX", chunk_size); + SaveLevel_CUSX(file, level, element); + } + } + + for (i = 0; i < NUM_GROUP_ELEMENTS; i++) + { + int element = EL_GROUP_START + i; + + chunk_size = SaveLevel_GRPX(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_GRPX_UNCHANGED) // save if changed + { + putFileChunkBE(file, "GRPX", chunk_size); + SaveLevel_GRPX(file, level, element); + } + } - putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE); - SaveTape_HEAD(file, &tape); + for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++) + { + int element = GET_EMPTY_ELEMENT(i); - if (checkSaveTape_SCRN(&tape)) - { - putFileChunkBE(file, "SCRN", TAPE_CHUNK_SCRN_SIZE); - SaveTape_SCRN(file, &tape); + chunk_size = SaveLevel_EMPX(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED) // save if changed + { + putFileChunkBE(file, "EMPX", chunk_size); + SaveLevel_EMPX(file, level, element); + } + } } - putFileChunkBE(file, "INFO", info_chunk_size); - SaveTape_INFO(file, &tape); - - putFileChunkBE(file, "BODY", body_chunk_size); - SaveTape_BODY(file, &tape); - fclose(file); SetFilePermissions(filename, PERMS_PRIVATE); } -static void SaveTapeExt(char *filename) -{ - int i; - - tape.file_version = FILE_VERSION_ACTUAL; - tape.game_version = GAME_VERSION_ACTUAL; - - tape.num_participating_players = 0; - - // count number of participating players - for (i = 0; i < MAX_PLAYERS; i++) - if (tape.player_participates[i]) - tape.num_participating_players++; - - SaveTapeToFilename(filename); - - tape.changed = FALSE; -} - -void SaveTape(int nr) +void SaveLevel(int nr) { - char *filename = getTapeFilename(nr); - - InitTapeDirectory(leveldir_current->subdir); + char *filename = getDefaultLevelFilename(nr); - SaveTapeExt(filename); + SaveLevelFromFilename(&level, filename, FALSE); } -void SaveScoreTape(int nr) +void SaveLevelTemplate(void) { - char *filename = getScoreTapeFilename(tape.score_tape_basename, nr); - - // used instead of "leveldir_current->subdir" (for network games) - InitScoreTapeDirectory(levelset.identifier, nr); + char *filename = getLocalLevelTemplateFilename(); - SaveTapeExt(filename); + SaveLevelFromFilename(&level, filename, TRUE); } -static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved, - unsigned int req_state_added) +boolean SaveLevelChecked(int nr) { - char *filename = getTapeFilename(nr); - boolean new_tape = !fileExists(filename); - boolean tape_saved = FALSE; + char *filename = getDefaultLevelFilename(nr); + boolean new_level = !fileExists(filename); + boolean level_saved = FALSE; - if (new_tape || Request(msg_replace, REQ_ASK | req_state_added)) + if (new_level || Request("Save this level and kill the old?", REQ_ASK)) { - SaveTape(nr); + SaveLevel(nr); - if (new_tape) - Request(msg_saved, REQ_CONFIRM | req_state_added); + if (new_level) + Request("Level saved!", REQ_CONFIRM); - tape_saved = TRUE; + level_saved = TRUE; } - return tape_saved; -} - -boolean SaveTapeChecked(int nr) -{ - return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!", 0); -} - -boolean SaveTapeChecked_LevelSolved(int nr) -{ - return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?", - "Level solved! Tape saved!", REQ_STAY_OPEN); + return level_saved; } -void DumpTape(struct TapeInfo *tape) +void DumpLevel(struct LevelInfo *level) { - int tape_frame_counter; - int i, j; - - if (tape->no_valid_file) + if (level->no_level_file || level->no_valid_file) { - Warn("cannot dump -- no valid tape file found"); + Warn("cannot dump -- no valid level file found"); return; } PrintLine("-", 79); + Print("Level xxx (file version %08d, game version %08d)\n", + level->file_version, level->game_version); + PrintLine("-", 79); - Print("Tape of Level %03d (file version %08d, game version %08d)\n", - tape->level_nr, tape->file_version, tape->game_version); - Print(" (effective engine version %08d)\n", - tape->engine_version); - Print("Level series identifier: '%s'\n", tape->level_identifier); - - Print("Special tape properties: "); - if (tape->property_bits == TAPE_PROPERTY_NONE) - Print("[none]"); - if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG) - Print("[em_random_bug]"); - if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED) - Print("[game_speed]"); - if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE) - Print("[pause]"); - if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP) - Print("[single_step]"); - if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT) - Print("[snapshot]"); - if (tape->property_bits & TAPE_PROPERTY_REPLAYED) - Print("[replayed]"); - if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS) - Print("[tas_keys]"); - if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS) - Print("[small_graphics]"); + Print("Level author: '%s'\n", level->author); + Print("Level title: '%s'\n", level->name); + Print("\n"); + Print("Playfield size: %d x %d\n", level->fieldx, level->fieldy); + Print("\n"); + Print("Level time: %d seconds\n", level->time); + Print("Gems needed: %d\n", level->gems_needed); + Print("\n"); + Print("Time for magic wall: %d seconds\n", level->time_magic_wall); + Print("Time for wheel: %d seconds\n", level->time_wheel); + Print("Time for light: %d seconds\n", level->time_light); + Print("Time for timegate: %d seconds\n", level->time_timegate); + Print("\n"); + Print("Amoeba speed: %d\n", level->amoeba_speed); Print("\n"); - int year2 = tape->date / 10000; - int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); - int month_index_raw = (tape->date / 100) % 100; - int month_index = month_index_raw % 12; // prevent invalid index - int month = month_index + 1; - int day = tape->date % 100; - - Print("Tape date: %04d-%02d-%02d\n", year4, month, day); - - PrintLine("-", 79); - - tape_frame_counter = 0; + Print("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no")); + Print("Player blocks last field: %s\n", (level->block_last_field ? "yes" : "no")); + Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no")); + Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no")); + Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no")); + Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no")); - for (i = 0; i < tape->length; i++) + if (options.debug) { - if (i >= MAX_TAPE_LEN) - break; - - Print("%04d: ", i); + int i, j; - for (j = 0; j < MAX_PLAYERS; j++) + for (i = 0; i < NUM_ENVELOPES; i++) { - if (tape->player_participates[j]) - { - int action = tape->pos[i].action[j]; + char *text = level->envelope[i].text; + int text_len = strlen(text); + boolean has_text = FALSE; - Print("%d:%02x ", j, action); - Print("[%c%c%c%c|%c%c] - ", - (action & JOY_LEFT ? '<' : ' '), - (action & JOY_RIGHT ? '>' : ' '), - (action & JOY_UP ? '^' : ' '), - (action & JOY_DOWN ? 'v' : ' '), - (action & JOY_BUTTON_1 ? '1' : ' '), - (action & JOY_BUTTON_2 ? '2' : ' ')); + for (j = 0; j < text_len; j++) + if (text[j] != ' ' && text[j] != '\n') + has_text = TRUE; + + if (has_text) + { + Print("\n"); + Print("Envelope %d:\n'%s'\n", i + 1, text); } } - - Print("(%03d) ", tape->pos[i].delay); - Print("[%05d]\n", tape_frame_counter); - - tape_frame_counter += tape->pos[i].delay; } PrintLine("-", 79); } -void DumpTapes(void) +void DumpLevels(void) { - static LevelDirTree *dumptape_leveldir = NULL; - - dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first, - global.dumptape_leveldir); + static LevelDirTree *dumplevel_leveldir = NULL; - if (dumptape_leveldir == NULL) - Fail("no such level identifier: '%s'", global.dumptape_leveldir); + dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumplevel_leveldir); - if (global.dumptape_level_nr < dumptape_leveldir->first_level || - global.dumptape_level_nr > dumptape_leveldir->last_level) - Fail("no such level number: %d", global.dumptape_level_nr); + if (dumplevel_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumplevel_leveldir); - leveldir_current = dumptape_leveldir; + if (global.dumplevel_level_nr < dumplevel_leveldir->first_level || + global.dumplevel_level_nr > dumplevel_leveldir->last_level) + Fail("no such level number: %d", global.dumplevel_level_nr); - if (options.mytapes) - LoadTape(global.dumptape_level_nr); - else - LoadSolutionTape(global.dumptape_level_nr); + leveldir_current = dumplevel_leveldir; - DumpTape(&tape); + LoadLevel(global.dumplevel_level_nr); + DumpLevel(&level); CloseAllAndExit(0); } // ============================================================================ -// score file functions +// tape file functions // ============================================================================ -static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) +static void setTapeInfoToDefaults(void) { int i; - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - { - strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME); - strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME); - scores->entry[i].score = 0; - scores->entry[i].time = 0; - } + // always start with reliable default values (empty tape) + TapeErase(); + + // default values (also for pre-1.2 tapes) with only the first player + tape.player_participates[0] = TRUE; + for (i = 1; i < MAX_PLAYERS; i++) + tape.player_participates[i] = FALSE; + + // at least one (default: the first) player participates in every tape + tape.num_participating_players = 1; + + tape.property_bits = TAPE_PROPERTY_NONE; + + tape.level_nr = level_nr; + tape.counter = 0; + tape.changed = FALSE; + tape.solved = FALSE; + + tape.recording = FALSE; + tape.playing = FALSE; + tape.pausing = FALSE; + + tape.scr_fieldx = SCR_FIELDX_DEFAULT; + tape.scr_fieldy = SCR_FIELDY_DEFAULT; + + tape.no_info_chunk = TRUE; + tape.no_valid_file = FALSE; +} + +static int getTapePosSize(struct TapeInfo *tape) +{ + int tape_pos_size = 0; + + if (tape->use_key_actions) + tape_pos_size += tape->num_participating_players; + + if (tape->use_mouse_actions) + tape_pos_size += 3; // x and y position and mouse button mask - scores->num_entries = 0; - scores->last_added = -1; - scores->last_added_local = -1; + tape_pos_size += 1; // tape action delay value - scores->updated = FALSE; - scores->uploaded = FALSE; - scores->force_last_added = FALSE; + return tape_pos_size; } -static void setScoreInfoToDefaults(void) +static void setTapeActionFlags(struct TapeInfo *tape, int value) { - setScoreInfoToDefaultsExt(&scores); + tape->use_key_actions = FALSE; + tape->use_mouse_actions = FALSE; + + if (value != TAPE_USE_MOUSE_ACTIONS_ONLY) + tape->use_key_actions = TRUE; + + if (value != TAPE_USE_KEY_ACTIONS_ONLY) + tape->use_mouse_actions = TRUE; } -static void setServerScoreInfoToDefaults(void) +static int getTapeActionValue(struct TapeInfo *tape) { - setScoreInfoToDefaultsExt(&server_scores); + return (tape->use_key_actions && + tape->use_mouse_actions ? TAPE_USE_KEY_AND_MOUSE_ACTIONS : + tape->use_key_actions ? TAPE_USE_KEY_ACTIONS_ONLY : + tape->use_mouse_actions ? TAPE_USE_MOUSE_ACTIONS_ONLY : + TAPE_ACTIONS_DEFAULT); } -static void LoadScore_OLD(int nr) +static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape) { - int i; - char *filename = getScoreFilename(nr); - char cookie[MAX_LINE_LEN]; - char line[MAX_LINE_LEN]; - char *line_ptr; - FILE *file; - - if (!(file = fopen(filename, MODE_READ))) - return; - - // check file identifier - if (fgets(cookie, MAX_LINE_LEN, file) == NULL) - cookie[0] = '\0'; - if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') - cookie[strlen(cookie) - 1] = '\0'; + tape->file_version = getFileVersion(file); + tape->game_version = getFileVersion(file); - if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) - { - Warn("unknown format of score file '%s'", filename); + return chunk_size; +} - fclose(file); +static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) +{ + int i; - return; - } + tape->random_seed = getFile32BitBE(file); + tape->date = getFile32BitBE(file); + tape->length = getFile32BitBE(file); - for (i = 0; i < MAX_SCORE_ENTRIES; i++) + // read header fields that are new since version 1.2 + if (tape->file_version >= FILE_VERSION_1_2) { - if (fscanf(file, "%d", &scores.entry[i].score) == EOF) - Warn("fscanf() failed; %s", strerror(errno)); - - if (fgets(line, MAX_LINE_LEN, file) == NULL) - line[0] = '\0'; - - if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') - line[strlen(line) - 1] = '\0'; + byte store_participating_players = getFile8Bit(file); + int engine_version; - for (line_ptr = line; *line_ptr; line_ptr++) + // since version 1.2, tapes store which players participate in the tape + tape->num_participating_players = 0; + for (i = 0; i < MAX_PLAYERS; i++) { - if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') + tape->player_participates[i] = FALSE; + + if (store_participating_players & (1 << i)) { - strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN); - scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; - break; + tape->player_participates[i] = TRUE; + tape->num_participating_players++; } } - } - - fclose(file); -} - -static void ConvertScore_OLD(void) -{ - // only convert score to time for levels that rate playing time over score - if (!level.rate_time_over_score) - return; - // convert old score to playing time for score-less levels (like Supaplex) - int time_final_max = 999; - int i; + setTapeActionFlags(tape, getFile8Bit(file)); - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - { - int score = scores.entry[i].score; + tape->property_bits = getFile8Bit(file); + tape->solved = getFile8Bit(file); - if (score > 0 && score < time_final_max) - scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND; + engine_version = getFileVersion(file); + if (engine_version > 0) + tape->engine_version = engine_version; + else + tape->engine_version = tape->game_version; } + + return chunk_size; } -static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores) +static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape) { - scores->file_version = getFileVersion(file); - scores->game_version = getFileVersion(file); + tape->scr_fieldx = getFile8Bit(file); + tape->scr_fieldy = getFile8Bit(file); return chunk_size; } -static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) +static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) { char *level_identifier = NULL; int level_identifier_size; int i; + tape->no_info_chunk = FALSE; + level_identifier_size = getFile16BitBE(file); level_identifier = checked_malloc(level_identifier_size); @@ -8679,91 +9183,218 @@ static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) for (i = 0; i < level_identifier_size; i++) level_identifier[i] = getFile8Bit(file); - strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN); - scores->level_identifier[MAX_FILENAME_LEN] = '\0'; + strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN); + tape->level_identifier[MAX_FILENAME_LEN] = '\0'; checked_free(level_identifier); - scores->level_nr = getFile16BitBE(file); - scores->num_entries = getFile16BitBE(file); + tape->level_nr = getFile16BitBE(file); - chunk_size = 2 + level_identifier_size + 2 + 2; + chunk_size = 2 + level_identifier_size + 2; return chunk_size; } -static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores) +static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) { int i, j; + int tape_pos_size = getTapePosSize(tape); + int chunk_size_expected = tape_pos_size * tape->length; - for (i = 0; i < scores->num_entries; i++) + if (chunk_size_expected != chunk_size) { - for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) - scores->entry[i].name[j] = getFile8Bit(file); + ReadUnusedBytesFromFile(file, chunk_size); + return chunk_size_expected; + } - scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; + for (i = 0; i < tape->length; i++) + { + if (i >= MAX_TAPE_LEN) + { + Warn("tape truncated -- size exceeds maximum tape size %d", + MAX_TAPE_LEN); + + // tape too large; read and ignore remaining tape data from this chunk + for (;i < tape->length; i++) + ReadUnusedBytesFromFile(file, tape_pos_size); + + break; + } + + if (tape->use_key_actions) + { + for (j = 0; j < MAX_PLAYERS; j++) + { + tape->pos[i].action[j] = MV_NONE; + + if (tape->player_participates[j]) + tape->pos[i].action[j] = getFile8Bit(file); + } + } + + if (tape->use_mouse_actions) + { + tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file); + tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file); + tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file); + } + + tape->pos[i].delay = getFile8Bit(file); + + if (tape->file_version == FILE_VERSION_1_0) + { + // eliminate possible diagonal moves in old tapes + // this is only for backward compatibility + + byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN }; + byte action = tape->pos[i].action[0]; + int k, num_moves = 0; + + for (k = 0; k < 4; k++) + { + if (action & joy_dir[k]) + { + tape->pos[i + num_moves].action[0] = joy_dir[k]; + if (num_moves > 0) + tape->pos[i + num_moves].delay = 0; + num_moves++; + } + } + + if (num_moves > 1) + { + num_moves--; + i += num_moves; + tape->length += num_moves; + } + } + else if (tape->file_version < FILE_VERSION_2_0) + { + // convert pre-2.0 tapes to new tape format + + if (tape->pos[i].delay > 1) + { + // action part + tape->pos[i + 1] = tape->pos[i]; + tape->pos[i + 1].delay = 1; + + // delay part + for (j = 0; j < MAX_PLAYERS; j++) + tape->pos[i].action[j] = MV_NONE; + tape->pos[i].delay--; + + i++; + tape->length++; + } + } + + if (checkEndOfFile(file)) + break; } - chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN; + if (i != tape->length) + chunk_size = tape_pos_size * i; return chunk_size; } -static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) +static void LoadTape_SokobanSolution(char *filename) { - int i; + File *file; + int move_delay = TILESIZE / level.initial_player_stepsize[0]; - for (i = 0; i < scores->num_entries; i++) - scores->entry[i].score = getFile16BitBE(file); + if (!(file = openFile(filename, MODE_READ))) + { + tape.no_valid_file = TRUE; - chunk_size = scores->num_entries * 2; + return; + } - return chunk_size; -} + while (!checkEndOfFile(file)) + { + unsigned char c = getByteFromFile(file); + + if (checkEndOfFile(file)) + break; + + switch (c) + { + case 'u': + case 'U': + tape.pos[tape.length].action[0] = MV_UP; + tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); + tape.length++; + break; -static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) -{ - int i; + case 'd': + case 'D': + tape.pos[tape.length].action[0] = MV_DOWN; + tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); + tape.length++; + break; - for (i = 0; i < scores->num_entries; i++) - scores->entry[i].time = getFile32BitBE(file); + case 'l': + case 'L': + tape.pos[tape.length].action[0] = MV_LEFT; + tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); + tape.length++; + break; - chunk_size = scores->num_entries * 4; + case 'r': + case 'R': + tape.pos[tape.length].action[0] = MV_RIGHT; + tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0); + tape.length++; + break; - return chunk_size; -} + case '\n': + case '\r': + case '\t': + case ' ': + // ignore white-space characters + break; -static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) -{ - int i, j; + default: + tape.no_valid_file = TRUE; - for (i = 0; i < scores->num_entries; i++) - { - for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) - scores->entry[i].tape_basename[j] = getFile8Bit(file); + Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c); - scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + break; + } } - chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + closeFile(file); - return chunk_size; + if (tape.no_valid_file) + return; + + tape.length_frames = GetTapeLengthFrames(); + tape.length_seconds = GetTapeLengthSeconds(); } -void LoadScore(int nr) +void LoadTapeFromFilename(char *filename) { - char *filename = getScoreFilename(nr); char cookie[MAX_LINE_LEN]; char chunk_name[CHUNK_ID_LEN + 1]; - int chunk_size; - boolean old_score_file_format = FALSE; File *file; + int chunk_size; // always start with reliable default values - setScoreInfoToDefaults(); + setTapeInfoToDefaults(); + + if (strSuffix(filename, ".sln")) + { + LoadTape_SokobanSolution(filename); + + return; + } if (!(file = openFile(filename, MODE_READ))) + { + tape.no_valid_file = TRUE; + return; + } getFileChunkBE(file, chunk_name, NULL); if (strEqual(chunk_name, "RND1")) @@ -8771,16 +9402,18 @@ void LoadScore(int nr) getFile32BitBE(file); // not used getFileChunkBE(file, chunk_name, NULL); - if (!strEqual(chunk_name, "SCOR")) + if (!strEqual(chunk_name, "TAPE")) { - Warn("unknown format of score file '%s'", filename); + tape.no_valid_file = TRUE; + + Warn("unknown format of tape file '%s'", filename); closeFile(file); return; } } - else // check for old file format with cookie string + else // check for pre-2.0 file format with cookie string { strcpy(cookie, chunk_name); if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) @@ -8788,25 +9421,37 @@ void LoadScore(int nr) if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') cookie[strlen(cookie) - 1] = '\0'; - if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + if (!checkCookieString(cookie, TAPE_COOKIE_TMPL)) { - Warn("unknown format of score file '%s'", filename); + tape.no_valid_file = TRUE; + + Warn("unknown format of tape file '%s'", filename); closeFile(file); return; } - old_score_file_format = TRUE; + if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1) + { + tape.no_valid_file = TRUE; + + Warn("unsupported version of tape file '%s'", filename); + + closeFile(file); + + return; + } + + // pre-2.0 tape files have no game version, so use file version here + tape.game_version = tape.file_version; } - if (old_score_file_format) + if (tape.file_version < FILE_VERSION_1_2) { - // score files from versions before 4.2.4.0 without chunk structure - LoadScore_OLD(nr); - - // convert score to time, if possible (mainly for Supaplex levels) - ConvertScore_OLD(); + // tape files from versions before 1.2.0 without chunk structure + LoadTape_HEAD(file, TAPE_CHUNK_HEAD_SIZE, &tape); + LoadTape_BODY(file, 2 * tape.length, &tape); } else { @@ -8814,17 +9459,15 @@ void LoadScore(int nr) { char *name; int size; - int (*loader)(File *, int, struct ScoreInfo *); + int (*loader)(File *, int, struct TapeInfo *); } chunk_info[] = { - { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS }, - { "INFO", -1, LoadScore_INFO }, - { "NAME", -1, LoadScore_NAME }, - { "SCOR", -1, LoadScore_SCOR }, - { "TIME", -1, LoadScore_TIME }, - { "TAPE", -1, LoadScore_TAPE }, - + { "VERS", TAPE_CHUNK_VERS_SIZE, LoadTape_VERS }, + { "HEAD", TAPE_CHUNK_HEAD_SIZE, LoadTape_HEAD }, + { "SCRN", TAPE_CHUNK_SCRN_SIZE, LoadTape_SCRN }, + { "INFO", -1, LoadTape_INFO }, + { "BODY", -1, LoadTape_BODY }, { NULL, 0, NULL } }; @@ -8832,915 +9475,1053 @@ void LoadScore(int nr) { int i = 0; - while (chunk_info[i].name != NULL && - !strEqual(chunk_name, chunk_info[i].name)) - i++; + while (chunk_info[i].name != NULL && + !strEqual(chunk_name, chunk_info[i].name)) + i++; + + if (chunk_info[i].name == NULL) + { + Warn("unknown chunk '%s' in tape file '%s'", + chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in tape file '%s'", + chunk_size, chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this tape chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &tape); + + // the size of some chunks cannot be checked before reading other + // chunks first (like "HEAD" and "BODY") that contain some header + // information, so check them here + if (chunk_size_expected != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in tape file '%s'", + chunk_size, chunk_name, filename); + } + } + } + } + + closeFile(file); + + tape.length_frames = GetTapeLengthFrames(); + tape.length_seconds = GetTapeLengthSeconds(); + +#if 0 + Debug("files:LoadTapeFromFilename", "tape file version: %d", + tape.file_version); + Debug("files:LoadTapeFromFilename", "tape game version: %d", + tape.game_version); + Debug("files:LoadTapeFromFilename", "tape engine version: %d", + tape.engine_version); +#endif +} + +void LoadTape(int nr) +{ + char *filename = getTapeFilename(nr); + + LoadTapeFromFilename(filename); +} + +void LoadSolutionTape(int nr) +{ + char *filename = getSolutionTapeFilename(nr); + + LoadTapeFromFilename(filename); + + if (TAPE_IS_EMPTY(tape)) + { + if (level.game_engine_type == GAME_ENGINE_TYPE_BD && + level.native_bd_level->replay != NULL) + CopyNativeTape_BD_to_RND(&level); + else if (level.game_engine_type == GAME_ENGINE_TYPE_SP && + level.native_sp_level->demo.is_available) + CopyNativeTape_SP_to_RND(&level); + } +} + +void LoadScoreTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + +void LoadScoreCacheTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreCacheTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + +static boolean checkSaveTape_SCRN(struct TapeInfo *tape) +{ + // chunk required for team mode tapes with non-default screen size + return (tape->num_participating_players > 1 && + (tape->scr_fieldx != SCR_FIELDX_DEFAULT || + tape->scr_fieldy != SCR_FIELDY_DEFAULT)); +} + +static void SaveTape_VERS(FILE *file, struct TapeInfo *tape) +{ + putFileVersion(file, tape->file_version); + putFileVersion(file, tape->game_version); +} + +static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) +{ + int i; + byte store_participating_players = 0; + + // set bits for participating players for compact storage + for (i = 0; i < MAX_PLAYERS; i++) + if (tape->player_participates[i]) + store_participating_players |= (1 << i); + + putFile32BitBE(file, tape->random_seed); + putFile32BitBE(file, tape->date); + putFile32BitBE(file, tape->length); + + putFile8Bit(file, store_participating_players); + + putFile8Bit(file, getTapeActionValue(tape)); + + putFile8Bit(file, tape->property_bits); + putFile8Bit(file, tape->solved); + + putFileVersion(file, tape->engine_version); +} + +static void SaveTape_SCRN(FILE *file, struct TapeInfo *tape) +{ + putFile8Bit(file, tape->scr_fieldx); + putFile8Bit(file, tape->scr_fieldy); +} + +static void SaveTape_INFO(FILE *file, struct TapeInfo *tape) +{ + int level_identifier_size = strlen(tape->level_identifier) + 1; + int i; + + putFile16BitBE(file, level_identifier_size); + + for (i = 0; i < level_identifier_size; i++) + putFile8Bit(file, tape->level_identifier[i]); - if (chunk_info[i].name == NULL) - { - Warn("unknown chunk '%s' in score file '%s'", - chunk_name, filename); + putFile16BitBE(file, tape->level_nr); +} - ReadUnusedBytesFromFile(file, chunk_size); - } - else if (chunk_info[i].size != -1 && - chunk_info[i].size != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in score file '%s'", - chunk_size, chunk_name, filename); +static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) +{ + int i, j; - ReadUnusedBytesFromFile(file, chunk_size); - } - else - { - // call function to load this score chunk - int chunk_size_expected = - (chunk_info[i].loader)(file, chunk_size, &scores); + for (i = 0; i < tape->length; i++) + { + if (tape->use_key_actions) + { + for (j = 0; j < MAX_PLAYERS; j++) + if (tape->player_participates[j]) + putFile8Bit(file, tape->pos[i].action[j]); + } - // the size of some chunks cannot be checked before reading other - // chunks first (like "HEAD" and "BODY") that contain some header - // information, so check them here - if (chunk_size_expected != chunk_size) - { - Warn("wrong size (%d) of chunk '%s' in score file '%s'", - chunk_size, chunk_name, filename); - } - } + if (tape->use_mouse_actions) + { + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]); + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]); + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]); } - } - closeFile(file); + putFile8Bit(file, tape->pos[i].delay); + } } -#if ENABLE_HISTORIC_CHUNKS -void SaveScore_OLD(int nr) +void SaveTapeToFilename(char *filename) { - int i; - char *filename = getScoreFilename(nr); FILE *file; - - // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + int tape_pos_size; + int info_chunk_size; + int body_chunk_size; if (!(file = fopen(filename, MODE_WRITE))) { - Warn("cannot save score for level %d", nr); + Warn("cannot save level recording file '%s'", filename); return; } - fprintf(file, "%s\n\n", SCORE_COOKIE); + tape_pos_size = getTapePosSize(&tape); - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name); + info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2; + body_chunk_size = tape_pos_size * tape.length; + + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE); + + putFileChunkBE(file, "VERS", TAPE_CHUNK_VERS_SIZE); + SaveTape_VERS(file, &tape); + + putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE); + SaveTape_HEAD(file, &tape); + + if (checkSaveTape_SCRN(&tape)) + { + putFileChunkBE(file, "SCRN", TAPE_CHUNK_SCRN_SIZE); + SaveTape_SCRN(file, &tape); + } + + putFileChunkBE(file, "INFO", info_chunk_size); + SaveTape_INFO(file, &tape); + + putFileChunkBE(file, "BODY", body_chunk_size); + SaveTape_BODY(file, &tape); fclose(file); SetFilePermissions(filename, PERMS_PRIVATE); } -#endif -static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores) +static void SaveTapeExt(char *filename) { - putFileVersion(file, scores->file_version); - putFileVersion(file, scores->game_version); + int i; + + tape.file_version = FILE_VERSION_ACTUAL; + tape.game_version = GAME_VERSION_ACTUAL; + + tape.num_participating_players = 0; + + // count number of participating players + for (i = 0; i < MAX_PLAYERS; i++) + if (tape.player_participates[i]) + tape.num_participating_players++; + + SaveTapeToFilename(filename); + + tape.changed = FALSE; } -static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores) +void SaveTape(int nr) { - int level_identifier_size = strlen(scores->level_identifier) + 1; - int i; + char *filename = getTapeFilename(nr); - putFile16BitBE(file, level_identifier_size); + InitTapeDirectory(leveldir_current->subdir); - for (i = 0; i < level_identifier_size; i++) - putFile8Bit(file, scores->level_identifier[i]); + SaveTapeExt(filename); +} - putFile16BitBE(file, scores->level_nr); - putFile16BitBE(file, scores->num_entries); +void SaveScoreTape(int nr) +{ + char *filename = getScoreTapeFilename(tape.score_tape_basename, nr); + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreTapeDirectory(levelset.identifier, nr); + + SaveTapeExt(filename); } -static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores) +static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved, + unsigned int req_state_added) { - int i, j; + char *filename = getTapeFilename(nr); + boolean new_tape = !fileExists(filename); + boolean tape_saved = FALSE; - for (i = 0; i < scores->num_entries; i++) + if (new_tape || Request(msg_replace, REQ_ASK | req_state_added)) { - int name_size = strlen(scores->entry[i].name); + SaveTape(nr); - for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) - putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0)); + if (new_tape) + Request(msg_saved, REQ_CONFIRM | req_state_added); + + tape_saved = TRUE; } + + return tape_saved; } -static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) +boolean SaveTapeChecked(int nr) { - int i; - - for (i = 0; i < scores->num_entries; i++) - putFile16BitBE(file, scores->entry[i].score); + return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!", 0); } -static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) +boolean SaveTapeChecked_LevelSolved(int nr) { - int i; - - for (i = 0; i < scores->num_entries; i++) - putFile32BitBE(file, scores->entry[i].time); + return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?", + "Level solved! Tape saved!", REQ_STAY_OPEN); } -static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores) +void DumpTape(struct TapeInfo *tape) { + int tape_frame_counter; int i, j; - for (i = 0; i < scores->num_entries; i++) + if (tape->no_valid_file) { - int size = strlen(scores->entry[i].tape_basename); + Warn("cannot dump -- no valid tape file found"); - for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) - putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0)); + return; } -} -static void SaveScoreToFilename(char *filename) -{ - FILE *file; - int info_chunk_size; - int name_chunk_size; - int scor_chunk_size; - int time_chunk_size; - int tape_chunk_size; + PrintLine("-", 79); - if (!(file = fopen(filename, MODE_WRITE))) - { - Warn("cannot save score file '%s'", filename); + Print("Tape of Level %03d (file version %08d, game version %08d)\n", + tape->level_nr, tape->file_version, tape->game_version); + Print(" (effective engine version %08d)\n", + tape->engine_version); + Print("Level series identifier: '%s'\n", tape->level_identifier); - return; - } + Print("Solution tape: %s\n", + tape->solved ? "yes" : + tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no"); - info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2; - name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN; - scor_chunk_size = scores.num_entries * 2; - time_chunk_size = scores.num_entries * 4; - tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + Print("Special tape properties: "); + if (tape->property_bits == TAPE_PROPERTY_NONE) + Print("[none]"); + if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG) + Print("[em_random_bug]"); + if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED) + Print("[game_speed]"); + if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE) + Print("[pause]"); + if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP) + Print("[single_step]"); + if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT) + Print("[snapshot]"); + if (tape->property_bits & TAPE_PROPERTY_REPLAYED) + Print("[replayed]"); + if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS) + Print("[tas_keys]"); + if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS) + Print("[small_graphics]"); + Print("\n"); - putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); - putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); + int year2 = tape->date / 10000; + int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); + int month_index_raw = (tape->date / 100) % 100; + int month_index = month_index_raw % 12; // prevent invalid index + int month = month_index + 1; + int day = tape->date % 100; - putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE); - SaveScore_VERS(file, &scores); + Print("Tape date: %04d-%02d-%02d\n", year4, month, day); - putFileChunkBE(file, "INFO", info_chunk_size); - SaveScore_INFO(file, &scores); + PrintLine("-", 79); - putFileChunkBE(file, "NAME", name_chunk_size); - SaveScore_NAME(file, &scores); + tape_frame_counter = 0; - putFileChunkBE(file, "SCOR", scor_chunk_size); - SaveScore_SCOR(file, &scores); + for (i = 0; i < tape->length; i++) + { + if (i >= MAX_TAPE_LEN) + break; - putFileChunkBE(file, "TIME", time_chunk_size); - SaveScore_TIME(file, &scores); + Print("%04d: ", i); + + for (j = 0; j < MAX_PLAYERS; j++) + { + if (tape->player_participates[j]) + { + int action = tape->pos[i].action[j]; + + Print("%d:%02x ", j, action); + Print("[%c%c%c%c|%c%c] - ", + (action & JOY_LEFT ? '<' : ' '), + (action & JOY_RIGHT ? '>' : ' '), + (action & JOY_UP ? '^' : ' '), + (action & JOY_DOWN ? 'v' : ' '), + (action & JOY_BUTTON_1 ? '1' : ' '), + (action & JOY_BUTTON_2 ? '2' : ' ')); + } + } - putFileChunkBE(file, "TAPE", tape_chunk_size); - SaveScore_TAPE(file, &scores); + Print("(%03d) ", tape->pos[i].delay); + Print("[%05d]\n", tape_frame_counter); - fclose(file); + tape_frame_counter += tape->pos[i].delay; + } - SetFilePermissions(filename, PERMS_PRIVATE); + PrintLine("-", 79); } -void SaveScore(int nr) +void DumpTapes(void) { - char *filename = getScoreFilename(nr); - int i; + static LevelDirTree *dumptape_leveldir = NULL; - // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumptape_leveldir); - scores.file_version = FILE_VERSION_ACTUAL; - scores.game_version = GAME_VERSION_ACTUAL; + if (dumptape_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumptape_leveldir); - strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN); - scores.level_identifier[MAX_FILENAME_LEN] = '\0'; - scores.level_nr = level_nr; + if (global.dumptape_level_nr < dumptape_leveldir->first_level || + global.dumptape_level_nr > dumptape_leveldir->last_level) + Fail("no such level number: %d", global.dumptape_level_nr); - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - if (scores.entry[i].score == 0 && - scores.entry[i].time == 0 && - strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME)) - break; + leveldir_current = dumptape_leveldir; - scores.num_entries = i; + if (options.mytapes) + LoadTape(global.dumptape_level_nr); + else + LoadSolutionTape(global.dumptape_level_nr); - if (scores.num_entries == 0) - return; + DumpTape(&tape); - SaveScoreToFilename(filename); + CloseAllAndExit(0); } -void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data, - char *error) -{ -#if defined(PLATFORM_EMSCRIPTEN) - // threads currently not fully supported by Emscripten/SDL and some browsers - function(data); -#else - SDL_Thread *thread = SDL_CreateThread(function, name, data); - - if (thread != NULL) - SDL_DetachThread(thread); - else - Error("Cannot create thread to %s!", error); - // nasty kludge to lower probability of intermingled thread error messages - Delay(1); -#endif -} +// ============================================================================ +// score file functions +// ============================================================================ -char *getPasswordJSON(char *password) +static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) { - static char password_json[MAX_FILENAME_LEN] = ""; - static boolean initialized = FALSE; + int i; - if (!initialized) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (password != NULL && - !strEqual(password, "") && - !strEqual(password, UNDEFINED_PASSWORD)) - snprintf(password_json, MAX_FILENAME_LEN, - " \"password\": \"%s\",\n", - setup.api_server_password); + strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME); + strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME); + scores->entry[i].score = 0; + scores->entry[i].time = 0; - initialized = TRUE; + scores->entry[i].id = -1; + strcpy(scores->entry[i].tape_date, UNKNOWN_NAME); + strcpy(scores->entry[i].platform, UNKNOWN_NAME); + strcpy(scores->entry[i].version, UNKNOWN_NAME); + strcpy(scores->entry[i].country_name, UNKNOWN_NAME); + strcpy(scores->entry[i].country_code, "??"); } - return password_json; + scores->num_entries = 0; + scores->last_added = -1; + scores->last_added_local = -1; + + scores->updated = FALSE; + scores->uploaded = FALSE; + scores->tape_downloaded = FALSE; + scores->force_last_added = FALSE; + + // The following values are intentionally not reset here: + // - last_level_nr + // - last_entry_nr + // - next_level_nr + // - continue_playing + // - continue_on_return } -struct ApiGetScoreThreadData +static void setScoreInfoToDefaults(void) { - int level_nr; - char *score_cache_filename; -}; + setScoreInfoToDefaultsExt(&scores); +} -static void *CreateThreadData_ApiGetScore(int nr) +static void setServerScoreInfoToDefaults(void) { - struct ApiGetScoreThreadData *data = - checked_malloc(sizeof(struct ApiGetScoreThreadData)); - char *score_cache_filename = getScoreCacheFilename(nr); - - data->level_nr = nr; - data->score_cache_filename = getStringCopy(score_cache_filename); - - return data; + setScoreInfoToDefaultsExt(&server_scores); } -static void FreeThreadData_ApiGetScore(void *data_raw) +static void LoadScore_OLD(int nr) { - struct ApiGetScoreThreadData *data = data_raw; + int i; + char *filename = getScoreFilename(nr); + char cookie[MAX_LINE_LEN]; + char line[MAX_LINE_LEN]; + char *line_ptr; + FILE *file; - checked_free(data->score_cache_filename); - checked_free(data); -} + if (!(file = fopen(filename, MODE_READ))) + return; -static boolean SetRequest_ApiGetScore(struct HttpRequest *request, - void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - int level_nr = data->level_nr; + // check file identifier + if (fgets(cookie, MAX_LINE_LEN, file) == NULL) + cookie[0] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + fclose(file); - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_GET; + return; + } - char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); - char *levelset_name = getEscapedJSON(leveldir_current->name); + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + if (fscanf(file, "%d", &scores.entry[i].score) == EOF) + Warn("fscanf() failed; %s", strerror(errno)); - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"levelset_name\": \"%s\",\n" - " \"level_nr\": \"%d\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - levelset_identifier, - levelset_name, - level_nr); + if (fgets(line, MAX_LINE_LEN, file) == NULL) + line[0] = '\0'; - checked_free(levelset_identifier); - checked_free(levelset_name); + if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; - ConvertHttpRequestBodyToServerEncoding(request); + for (line_ptr = line; *line_ptr; line_ptr++) + { + if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') + { + strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN); + scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; + break; + } + } + } - return TRUE; + fclose(file); } -static void HandleResponse_ApiGetScore(struct HttpResponse *response, - void *data_raw) +static void ConvertScore_OLD(void) { - struct ApiGetScoreThreadData *data = data_raw; + // only convert score to time for levels that rate playing time over score + if (!level.rate_time_over_score) + return; + + // convert old score to playing time for score-less levels (like Supaplex) + int time_final_max = 999; + int i; - if (response->body_size == 0) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - // no scores available for this level + int score = scores.entry[i].score; - return; + if (score > 0 && score < time_final_max) + scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND; } +} + +static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores) +{ + scores->file_version = getFileVersion(file); + scores->game_version = getFileVersion(file); - ConvertHttpResponseBodyToClientEncoding(response); + return chunk_size; +} - char *filename = data->score_cache_filename; - FILE *file; +static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) +{ + char *level_identifier = NULL; + int level_identifier_size; int i; - // used instead of "leveldir_current->subdir" (for network games) - InitScoreCacheDirectory(levelset.identifier); + level_identifier_size = getFile16BitBE(file); - if (!(file = fopen(filename, MODE_WRITE))) - { - Warn("cannot save score cache file '%s'", filename); + level_identifier = checked_malloc(level_identifier_size); - return; - } + for (i = 0; i < level_identifier_size; i++) + level_identifier[i] = getFile8Bit(file); - for (i = 0; i < response->body_size; i++) - fputc(response->body[i], file); + strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN); + scores->level_identifier[MAX_FILENAME_LEN] = '\0'; - fclose(file); + checked_free(level_identifier); - SetFilePermissions(filename, PERMS_PRIVATE); + scores->level_nr = getFile16BitBE(file); + scores->num_entries = getFile16BitBE(file); + + chunk_size = 2 + level_identifier_size + 2 + 2; - server_scores.updated = TRUE; + return chunk_size; } -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) +static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores) { - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + int i, j; - if (response != NULL) + for (i = 0; i < scores->num_entries; i++) { - HandleResponse_ApiGetScore(response, data_raw); + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + scores->entry[i].name[j] = getFile8Bit(file); - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); + scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; } - FreeThreadData_ApiGetScore(data_raw); + chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN; + + return chunk_size; } -static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) +static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) { - Error("server failed to handle request: %d %s", code, status); + int i; - FreeThreadData_ApiGetScore(data_raw); -} + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile16BitBE(file); -static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here + chunk_size = scores->num_entries * 2; + + return chunk_size; } -static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request, - void *data_raw) +static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores) { - if (!SetRequest_ApiGetScore(request, data_raw)) - { - FreeThreadData_ApiGetScore(data_raw); + int i; - return; - } + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile32BitBE(file); - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiGetScore_Loaded, - Emscripten_ApiGetScore_Failed, - Emscripten_ApiGetScore_Progress); -} + chunk_size = scores->num_entries * 4; -#else + return chunk_size; +} -static void ApiGetScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) +static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) { - if (!SetRequest_ApiGetScore(request, data_raw)) - return; + int i; - if (!DoHttpRequest(request, response)) - { - Error("HTTP request failed: %s", GetHttpError()); + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].time = getFile32BitBE(file); - return; - } + chunk_size = scores->num_entries * 4; - if (!HTTP_SUCCESS(response->status_code)) - { - // do not show error message if no scores found for this level set - if (response->status_code == 404) - return; + return chunk_size; +} - Error("server failed to handle request: %d %s", - response->status_code, - response->status_text); +static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; - return; - } + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + scores->entry[i].tape_basename[j] = getFile8Bit(file); - HandleResponse_ApiGetScore(response, data_raw); -} + scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + } -static void ApiGetScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiGetScore_HttpRequestExt(request, response, data_raw); + chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; - FreeThreadData_ApiGetScore(data_raw); + return chunk_size; } -#endif -static int ApiGetScoreThread(void *data_raw) +void LoadScore(int nr) { - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - - program.api_thread_count++; - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiGetScore_HttpRequest(request, data_raw); -#else - ApiGetScore_HttpRequest(request, response, data_raw); -#endif + char *filename = getScoreFilename(nr); + char cookie[MAX_LINE_LEN]; + char chunk_name[CHUNK_ID_LEN + 1]; + int chunk_size; + boolean old_score_file_format = FALSE; + File *file; - program.api_thread_count--; + // always start with reliable default values + setScoreInfoToDefaults(); - checked_free(request); - checked_free(response); + if (!(file = openFile(filename, MODE_READ))) + return; - return 0; -} + getFileChunkBE(file, chunk_name, NULL); + if (strEqual(chunk_name, "RND1")) + { + getFile32BitBE(file); // not used -static void ApiGetScoreAsThread(int nr) -{ - struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr); + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "SCOR")) + { + Warn("unknown format of score file '%s'", filename); - ExecuteAsThread(ApiGetScoreThread, - "ApiGetScore", data, - "download scores from server"); -} + closeFile(file); -static void LoadServerScoreFromCache(int nr) -{ - struct ScoreEntry score_entry; - struct - { - void *value; - boolean is_string; - int string_size; + return; + } } - score_mapping[] = + else // check for old file format with cookie string { - { &score_entry.score, FALSE, 0 }, - { &score_entry.time, FALSE, 0 }, - { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN }, - { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN }, + strcpy(cookie, chunk_name); + if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) + cookie[4] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; - { NULL, FALSE, 0 } - }; - char *filename = getScoreCacheFilename(nr); - SetupFileHash *score_hash = loadSetupFileHash(filename); - int i, j; + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); - server_scores.num_entries = 0; + closeFile(file); - if (score_hash == NULL) - return; + return; + } - for (i = 0; i < MAX_SCORE_ENTRIES; i++) + old_score_file_format = TRUE; + } + + if (old_score_file_format) { - score_entry = server_scores.entry[i]; + // score files from versions before 4.2.4.0 without chunk structure + LoadScore_OLD(nr); - for (j = 0; score_mapping[j].value != NULL; j++) + // convert score to time, if possible (mainly for Supaplex levels) + ConvertScore_OLD(); + } + else + { + static struct { - char token[10]; + char *name; + int size; + int (*loader)(File *, int, struct ScoreInfo *); + } + chunk_info[] = + { + { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS }, + { "INFO", -1, LoadScore_INFO }, + { "NAME", -1, LoadScore_NAME }, + { "SCOR", -1, LoadScore_SCOR }, + { "SC4R", -1, LoadScore_SC4R }, + { "TIME", -1, LoadScore_TIME }, + { "TAPE", -1, LoadScore_TAPE }, - sprintf(token, "%02d.%d", i, j); + { NULL, 0, NULL } + }; - char *value = getHashEntry(score_hash, token); + while (getFileChunkBE(file, chunk_name, &chunk_size)) + { + int i = 0; - if (value == NULL) - continue; + while (chunk_info[i].name != NULL && + !strEqual(chunk_name, chunk_info[i].name)) + i++; - if (score_mapping[j].is_string) + if (chunk_info[i].name == NULL) { - char *score_value = (char *)score_mapping[j].value; - int value_size = score_mapping[j].string_size; + Warn("unknown chunk '%s' in score file '%s'", + chunk_name, filename); - strncpy(score_value, value, value_size); - score_value[value_size] = '\0'; + ReadUnusedBytesFromFile(file, chunk_size); } - else + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) { - int *score_value = (int *)score_mapping[j].value; + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); - *score_value = atoi(value); + ReadUnusedBytesFromFile(file, chunk_size); } + else + { + // call function to load this score chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &scores); - server_scores.num_entries = i + 1; + // the size of some chunks cannot be checked before reading other + // chunks first (like "HEAD" and "BODY") that contain some header + // information, so check them here + if (chunk_size_expected != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); + } + } } - - server_scores.entry[i] = score_entry; } - freeSetupFileHash(score_hash); -} - -void LoadServerScore(int nr, boolean download_score) -{ - if (!setup.use_api_server) - return; - - // always start with reliable default values - setServerScoreInfoToDefaults(); - - // 1st step: load server scores from cache file (which may not exist) - // (this should prevent reading it while the thread is writing to it) - LoadServerScoreFromCache(nr); - - if (download_score && runtime.use_api_server) - { - // 2nd step: download server scores from score server to cache file - // (as thread, as it might time out if the server is not reachable) - ApiGetScoreAsThread(nr); - } + closeFile(file); } -static char *get_file_base64(char *filename) +#if ENABLE_HISTORIC_CHUNKS +void SaveScore_OLD(int nr) { - struct stat file_status; - - if (stat(filename, &file_status) != 0) - { - Error("cannot stat file '%s'", filename); - - return NULL; - } - - int buffer_size = file_status.st_size; - byte *buffer = checked_malloc(buffer_size); - FILE *file; int i; + char *filename = getScoreFilename(nr); + FILE *file; - if (!(file = fopen(filename, MODE_READ))) - { - Error("cannot open file '%s'", filename); - - checked_free(buffer); - - return NULL; - } + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); - for (i = 0; i < buffer_size; i++) + if (!(file = fopen(filename, MODE_WRITE))) { - int c = fgetc(file); - - if (c == EOF) - { - Error("cannot read from input file '%s'", filename); - - fclose(file); - checked_free(buffer); - - return NULL; - } + Warn("cannot save score for level %d", nr); - buffer[i] = (byte)c; + return; } - fclose(file); + fprintf(file, "%s\n\n", SCORE_COOKIE); - int buffer_encoded_size = base64_encoded_size(buffer_size); - char *buffer_encoded = checked_malloc(buffer_encoded_size); + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name); - base64_encode(buffer_encoded, buffer, buffer_size); + fclose(file); - checked_free(buffer); + SetFilePermissions(filename, PERMS_PRIVATE); +} +#endif - return buffer_encoded; +static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores) +{ + putFileVersion(file, scores->file_version); + putFileVersion(file, scores->game_version); } -static void PrepareScoreTapesForUpload(char *leveldir_subdir) +static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores) { - MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); + int level_identifier_size = strlen(scores->level_identifier) + 1; + int i; - // if score tape not uploaded, ask for uploading missing tapes later - if (!setup.has_remaining_tapes) - setup.ask_for_remaining_tapes = TRUE; + putFile16BitBE(file, level_identifier_size); - setup.provide_uploading_tapes = TRUE; - setup.has_remaining_tapes = TRUE; + for (i = 0; i < level_identifier_size; i++) + putFile8Bit(file, scores->level_identifier[i]); - SaveSetup_ServerSetup(); + putFile16BitBE(file, scores->level_nr); + putFile16BitBE(file, scores->num_entries); } -struct ApiAddScoreThreadData +static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores) { - int level_nr; - boolean tape_saved; - char *leveldir_subdir; - char *score_tape_filename; - struct ScoreEntry score_entry; -}; + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + int name_size = strlen(scores->entry[i].name); + + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0)); + } +} -static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved, - char *score_tape_filename) +static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) { - struct ApiAddScoreThreadData *data = - checked_malloc(sizeof(struct ApiAddScoreThreadData)); - struct ScoreEntry *score_entry = &scores.entry[scores.last_added]; + int i; - if (score_tape_filename == NULL) - score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr); + for (i = 0; i < scores->num_entries; i++) + putFile16BitBE(file, scores->entry[i].score); +} - data->level_nr = nr; - data->tape_saved = tape_saved; - data->leveldir_subdir = getStringCopy(leveldir_current->subdir); - data->score_tape_filename = getStringCopy(score_tape_filename); - data->score_entry = *score_entry; +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) +{ + int i; - return data; + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); } -static void FreeThreadData_ApiAddScore(void *data_raw) +static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) { - struct ApiAddScoreThreadData *data = data_raw; + int i; - checked_free(data->leveldir_subdir); - checked_free(data->score_tape_filename); - checked_free(data); + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].time); } -static boolean SetRequest_ApiAddScore(struct HttpRequest *request, - void *data_raw) +static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores) { - struct ApiAddScoreThreadData *data = data_raw; - struct ScoreEntry *score_entry = &data->score_entry; - char *score_tape_filename = data->score_tape_filename; - boolean tape_saved = data->tape_saved; - int level_nr = data->level_nr; + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + int size = strlen(scores->entry[i].tape_basename); - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_ADD; + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0)); + } +} - char *tape_base64 = get_file_base64(score_tape_filename); +static void SaveScoreToFilename(char *filename) +{ + FILE *file; + int info_chunk_size; + int name_chunk_size; + int scor_chunk_size; + int sc4r_chunk_size; + int time_chunk_size; + int tape_chunk_size; + boolean has_large_score_values; + int i; - if (tape_base64 == NULL) + if (!(file = fopen(filename, MODE_WRITE))) { - Error("loading and base64 encoding score tape file failed"); + Warn("cannot save score file '%s'", filename); - return FALSE; + return; } - char *player_name_raw = score_entry->name; - char *player_uuid_raw = setup.player_uuid; - - if (options.player_name != NULL && global.autoplay_leveldir != NULL) - { - player_name_raw = options.player_name; - player_uuid_raw = ""; - } - - char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); - char *levelset_name = getEscapedJSON(leveldir_current->name); - char *levelset_author = getEscapedJSON(leveldir_current->author); - char *level_name = getEscapedJSON(level.name); - char *level_author = getEscapedJSON(level.author); - char *player_name = getEscapedJSON(player_name_raw); - char *player_uuid = getEscapedJSON(player_uuid_raw); - - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"batch_time\": \"%d\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"levelset_name\": \"%s\",\n" - " \"levelset_author\": \"%s\",\n" - " \"levelset_num_levels\": \"%d\",\n" - " \"levelset_first_level\": \"%d\",\n" - " \"level_nr\": \"%d\",\n" - " \"level_name\": \"%s\",\n" - " \"level_author\": \"%s\",\n" - " \"use_step_counter\": \"%d\",\n" - " \"rate_time_over_score\": \"%d\",\n" - " \"player_name\": \"%s\",\n" - " \"player_uuid\": \"%s\",\n" - " \"score\": \"%d\",\n" - " \"time\": \"%d\",\n" - " \"tape_basename\": \"%s\",\n" - " \"tape_saved\": \"%d\",\n" - " \"tape\": \"%s\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - (int)global.autoplay_time, - levelset_identifier, - levelset_name, - levelset_author, - leveldir_current->levels, - leveldir_current->first_level, - level_nr, - level_name, - level_author, - level.use_step_counter, - level.rate_time_over_score, - player_name, - player_uuid, - score_entry->score, - score_entry->time, - score_entry->tape_basename, - tape_saved, - tape_base64); - - checked_free(tape_base64); - - checked_free(levelset_identifier); - checked_free(levelset_name); - checked_free(levelset_author); - checked_free(level_name); - checked_free(level_author); - checked_free(player_name); - checked_free(player_uuid); - - ConvertHttpRequestBodyToServerEncoding(request); + info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2; + name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN; + scor_chunk_size = scores.num_entries * 2; + sc4r_chunk_size = scores.num_entries * 4; + time_chunk_size = scores.num_entries * 4; + tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; - return TRUE; -} + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; -static void HandleResponse_ApiAddScore(struct HttpResponse *response, - void *data_raw) -{ - server_scores.uploaded = TRUE; -} + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); -static void HandleFailure_ApiAddScore(void *data_raw) -{ - struct ApiAddScoreThreadData *data = data_raw; + putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE); + SaveScore_VERS(file, &scores); - PrepareScoreTapesForUpload(data->leveldir_subdir); -} + putFileChunkBE(file, "INFO", info_chunk_size); + SaveScore_INFO(file, &scores); -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + putFileChunkBE(file, "NAME", name_chunk_size); + SaveScore_NAME(file, &scores); - if (response != NULL) + if (has_large_score_values) { - HandleResponse_ApiAddScore(response, data_raw); - - checked_free(response); + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); } else { - Error("server response too large to handle (%d bytes)", size); - - HandleFailure_ApiAddScore(data_raw); + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); } - FreeThreadData_ApiAddScore(data_raw); -} + putFileChunkBE(file, "TIME", time_chunk_size); + SaveScore_TIME(file, &scores); -static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); + putFileChunkBE(file, "TAPE", tape_chunk_size); + SaveScore_TAPE(file, &scores); - HandleFailure_ApiAddScore(data_raw); + fclose(file); - FreeThreadData_ApiAddScore(data_raw); + SetFilePermissions(filename, PERMS_PRIVATE); } -static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) +void SaveScore(int nr) { - // nothing to do here + char *filename = getScoreFilename(nr); + int i; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + scores.file_version = FILE_VERSION_ACTUAL; + scores.game_version = GAME_VERSION_ACTUAL; + + strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN); + scores.level_identifier[MAX_FILENAME_LEN] = '\0'; + scores.level_nr = level_nr; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + if (scores.entry[i].score == 0 && + scores.entry[i].time == 0 && + strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME)) + break; + + scores.num_entries = i; + + if (scores.num_entries == 0) + return; + + SaveScoreToFilename(filename); } -static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request, - void *data_raw) +static void LoadServerScoreFromCache(int nr) { - if (!SetRequest_ApiAddScore(request, data_raw)) + struct ScoreEntry score_entry; + struct { - FreeThreadData_ApiAddScore(data_raw); - - return; + void *value; + boolean is_string; + int string_size; } + score_mapping[] = + { + { &score_entry.score, FALSE, 0 }, + { &score_entry.time, FALSE, 0 }, + { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN }, + { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN }, + { score_entry.tape_date, TRUE, MAX_ISO_DATE_LEN }, + { &score_entry.id, FALSE, 0 }, + { score_entry.platform, TRUE, MAX_PLATFORM_TEXT_LEN }, + { score_entry.version, TRUE, MAX_VERSION_TEXT_LEN }, + { score_entry.country_code, TRUE, MAX_COUNTRY_CODE_LEN }, + { score_entry.country_name, TRUE, MAX_COUNTRY_NAME_LEN }, - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiAddScore_Loaded, - Emscripten_ApiAddScore_Failed, - Emscripten_ApiAddScore_Progress); -} + { NULL, FALSE, 0 } + }; + char *filename = getScoreCacheFilename(nr); + SetupFileHash *score_hash = loadSetupFileHash(filename); + int i, j; -#else + server_scores.num_entries = 0; -static void ApiAddScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - if (!SetRequest_ApiAddScore(request, data_raw)) + if (score_hash == NULL) return; - if (!DoHttpRequest(request, response)) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - Error("HTTP request failed: %s", GetHttpError()); + score_entry = server_scores.entry[i]; + + for (j = 0; score_mapping[j].value != NULL; j++) + { + char token[10]; - HandleFailure_ApiAddScore(data_raw); + sprintf(token, "%02d.%d", i, j); - return; - } + char *value = getHashEntry(score_hash, token); - if (!HTTP_SUCCESS(response->status_code)) - { - Error("server failed to handle request: %d %s", - response->status_code, - response->status_text); + if (value == NULL) + continue; - HandleFailure_ApiAddScore(data_raw); + if (score_mapping[j].is_string) + { + char *score_value = (char *)score_mapping[j].value; + int value_size = score_mapping[j].string_size; - return; - } + strncpy(score_value, value, value_size); + score_value[value_size] = '\0'; + } + else + { + int *score_value = (int *)score_mapping[j].value; - HandleResponse_ApiAddScore(response, data_raw); -} + *score_value = atoi(value); + } -static void ApiAddScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiAddScore_HttpRequestExt(request, response, data_raw); + server_scores.num_entries = i + 1; + } - FreeThreadData_ApiAddScore(data_raw); + server_scores.entry[i] = score_entry; + } + + freeSetupFileHash(score_hash); } -#endif -static int ApiAddScoreThread(void *data_raw) +void LoadServerScore(int nr, boolean download_score) { - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - - program.api_thread_count++; - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiAddScore_HttpRequest(request, data_raw); -#else - ApiAddScore_HttpRequest(request, response, data_raw); -#endif + if (!setup.use_api_server) + return; - program.api_thread_count--; + // always start with reliable default values + setServerScoreInfoToDefaults(); - checked_free(request); - checked_free(response); + // 1st step: load server scores from cache file (which may not exist) + // (this should prevent reading it while the thread is writing to it) + LoadServerScoreFromCache(nr); - return 0; + if (download_score && runtime.use_api_server) + { + // 2nd step: download server scores from score server to cache file + // (as thread, as it might time out if the server is not reachable) + ApiGetScoreAsThread(nr); + } } -static void ApiAddScoreAsThread(int nr, boolean tape_saved, - char *score_tape_filename) +void PrepareScoreTapesForUpload(char *leveldir_subdir) { - struct ApiAddScoreThreadData *data = - CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename); + MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); + + // if score tape not uploaded, ask for uploading missing tapes later + if (!setup.has_remaining_tapes) + setup.ask_for_remaining_tapes = TRUE; + + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; - ExecuteAsThread(ApiAddScoreThread, - "ApiAddScore", data, - "upload score to server"); + SaveSetup_ServerSetup(); } void SaveServerScore(int nr, boolean tape_saved) @@ -9767,6 +10548,7 @@ void SaveServerScoreFromFile(int nr, boolean tape_saved, void LoadLocalAndServerScore(int nr, boolean download_score) { int last_added_local = scores.last_added_local; + boolean force_last_added = scores.force_last_added; // needed if only showing server scores setScoreInfoToDefaults(); @@ -9786,6 +10568,9 @@ void LoadLocalAndServerScore(int nr, boolean download_score) // merge local scores with scores from server MergeServerScore(); } + + if (force_last_added) + scores.force_last_added = force_last_added; } @@ -9826,6 +10611,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.toons, "toons" }, + { + TYPE_SWITCH, + &setup.global_animations, "global_animations" + }, { TYPE_SWITCH, &setup.scroll_delay, "scroll_delay" @@ -9854,6 +10643,14 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.autorecord, "automatic_tape_recording" }, + { + TYPE_SWITCH, + &setup.autorecord_after_replay, "autorecord_after_replay" + }, + { + TYPE_SWITCH, + &setup.auto_pause_on_start, "auto_pause_on_start" + }, { TYPE_SWITCH, &setup.show_titlescreen, "show_titlescreen" @@ -9874,6 +10671,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.skip_levels, "skip_levels" }, + { + TYPE_SWITCH_3_STATES, + &setup.allow_skipping_levels, "allow_skipping_levels" + }, { TYPE_SWITCH, &setup.increment_levels, "increment_levels" @@ -9962,6 +10763,62 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.game_frame_delay, "game_frame_delay" }, + { + TYPE_INTEGER, + &setup.default_game_engine_type, "default_game_engine_type" + }, + { + TYPE_SWITCH, + &setup.bd_skip_uncovering, "bd_skip_uncovering" + }, + { + TYPE_SWITCH, + &setup.bd_skip_hatching, "bd_skip_hatching" + }, + { + TYPE_SWITCH, + &setup.bd_scroll_delay, "bd_scroll_delay" + }, + { + TYPE_SWITCH, + &setup.bd_show_invisible_outbox, "bd_show_invisible_outbox" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_smooth_movements, "bd_smooth_movements" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_pushing_graphics, "bd_pushing_graphics" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_up_down_graphics, "bd_up_down_graphics" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_skip_falling_sounds, "bd_skip_falling_sounds" + }, + { + TYPE_INTEGER, + &setup.bd_palette_c64, "bd_palette_c64" + }, + { + TYPE_INTEGER, + &setup.bd_palette_c64dtv, "bd_palette_c64dtv" + }, + { + TYPE_INTEGER, + &setup.bd_palette_atari, "bd_palette_atari" + }, + { + TYPE_INTEGER, + &setup.bd_default_color_type, "bd_default_color_type" + }, + { + TYPE_SWITCH, + &setup.bd_random_colors, "bd_random_colors" + }, { TYPE_SWITCH, &setup.sp_show_border_elements, "sp_show_border_elements" @@ -9995,15 +10852,15 @@ static struct TokenInfo global_setup_tokens[] = &setup.music_set, "music_set" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_graphics, "override_level_graphics" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_sounds, "override_level_sounds" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_music, "override_level_music" }, { @@ -10018,6 +10875,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.volume_music, "volume_music" }, + { + TYPE_SWITCH, + &setup.audio_sample_rate_44100, "audio_sample_rate_44100" + }, { TYPE_SWITCH, &setup.network_mode, "network_mode" @@ -10070,6 +10931,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.touch.grid_ysize[1], "touch.virtual_buttons.1.ysize" }, + { + TYPE_SWITCH, + &setup.touch.overlay_buttons, "touch.overlay_buttons" + }, }; static struct TokenInfo auto_setup_tokens[] = @@ -10162,6 +11027,14 @@ static struct TokenInfo editor_cascade_setup_tokens[] = TYPE_SWITCH, &setup.editor_cascade.el_bd, "editor.cascade.el_bd" }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bdx, "editor.cascade.el_bdx" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bdx_effects, "editor.cascade.el_bdx_effects" + }, { TYPE_SWITCH, &setup.editor_cascade.el_em, "editor.cascade.el_em" @@ -10242,6 +11115,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.load_game, "shortcut.load_game" }, + { + TYPE_KEY_X11, + &setup.shortcut.restart_game, "shortcut.restart_game" + }, + { + TYPE_KEY_X11, + &setup.shortcut.pause_before_end, "shortcut.pause_before_end" + }, { TYPE_KEY_X11, &setup.shortcut.toggle_pause, "shortcut.toggle_pause" @@ -10318,6 +11199,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.snap_down, "shortcut.snap_down" }, + { + TYPE_KEY_X11, + &setup.shortcut.speed_fast, "shortcut.speed_fast" + }, + { + TYPE_KEY_X11, + &setup.shortcut.speed_slow, "shortcut.speed_slow" + }, }; static struct SetupInputInfo setup_input; @@ -10481,55 +11370,119 @@ static struct TokenInfo internal_setup_tokens[] = }, { TYPE_BOOLEAN, - &setup.internal.choose_from_top_leveldir, "choose_from_top_leveldir" + &setup.internal.choose_from_top_leveldir, "choose_from_top_leveldir" + }, + { + TYPE_BOOLEAN, + &setup.internal.show_scaling_in_title, "show_scaling_in_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.create_user_levelset, "create_user_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_screens_from_main, "info_screens_from_main" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_game, "menu_game" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_engines, "menu_engines" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_editor, "menu_editor" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_graphics, "menu_graphics" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_sound, "menu_sound" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_artwork, "menu_artwork" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_input, "menu_input" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_touch, "menu_touch" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts, "menu_shortcuts" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_exit, "menu_exit" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_save_and_exit, "menu_save_and_exit" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_various, "menu_shortcuts_various" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_focus, "menu_shortcuts_focus" }, { TYPE_BOOLEAN, - &setup.internal.show_scaling_in_title, "show_scaling_in_title" + &setup.internal.menu_shortcuts_tape, "menu_shortcuts_tape" }, { TYPE_BOOLEAN, - &setup.internal.create_user_levelset, "create_user_levelset" + &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound" }, { TYPE_BOOLEAN, - &setup.internal.menu_game, "menu_game" + &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap" }, { TYPE_BOOLEAN, - &setup.internal.menu_editor, "menu_editor" + &setup.internal.menu_shortcuts_speed, "menu_shortcuts_speed" }, { TYPE_BOOLEAN, - &setup.internal.menu_graphics, "menu_graphics" + &setup.internal.info_title, "info_title" }, { TYPE_BOOLEAN, - &setup.internal.menu_sound, "menu_sound" + &setup.internal.info_elements, "info_elements" }, { TYPE_BOOLEAN, - &setup.internal.menu_artwork, "menu_artwork" + &setup.internal.info_music, "info_music" }, { TYPE_BOOLEAN, - &setup.internal.menu_input, "menu_input" + &setup.internal.info_credits, "info_credits" }, { TYPE_BOOLEAN, - &setup.internal.menu_touch, "menu_touch" + &setup.internal.info_program, "info_program" }, { TYPE_BOOLEAN, - &setup.internal.menu_shortcuts, "menu_shortcuts" + &setup.internal.info_version, "info_version" }, { TYPE_BOOLEAN, - &setup.internal.menu_exit, "menu_exit" + &setup.internal.info_levelset, "info_levelset" }, { TYPE_BOOLEAN, - &setup.internal.menu_save_and_exit, "menu_save_and_exit" + &setup.internal.info_exit, "info_exit" }, }; @@ -10627,7 +11580,7 @@ static struct TokenInfo debug_setup_tokens[] = &setup.debug.show_frames_per_second, "debug.show_frames_per_second" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.debug.xsn_mode, "debug.xsn_mode" }, { @@ -10642,6 +11595,14 @@ static struct TokenInfo options_setup_tokens[] = TYPE_BOOLEAN, &setup.options.verbose, "options.verbose" }, + { + TYPE_BOOLEAN, + &setup.options.debug, "options.debug" + }, + { + TYPE_STRING, + &setup.options.debug_mode, "options.debug_mode" + }, }; static void setSetupInfoToDefaults(struct SetupInfo *si) @@ -10657,6 +11618,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sound_music = TRUE; si->sound_simple = TRUE; si->toons = TRUE; + si->global_animations = TRUE; si->scroll_delay = TRUE; si->forced_scroll_delay = FALSE; si->scroll_delay_value = STD_SCROLL_DELAY; @@ -10664,11 +11626,14 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; si->fade_screens = TRUE; si->autorecord = TRUE; + si->autorecord_after_replay = TRUE; + si->auto_pause_on_start = FALSE; si->show_titlescreen = TRUE; si->quick_doors = FALSE; si->team_mode = FALSE; si->handicap = TRUE; si->skip_levels = TRUE; + si->allow_skipping_levels = STATE_ASK; si->increment_levels = TRUE; si->auto_play_next_level = TRUE; si->count_score_after_game = TRUE; @@ -10691,6 +11656,20 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->prefer_extra_panel_items = TRUE; si->game_speed_extended = FALSE; si->game_frame_delay = GAME_FRAME_DELAY; + si->default_game_engine_type = GAME_ENGINE_TYPE_RND; + si->bd_skip_uncovering = FALSE; + si->bd_skip_hatching = FALSE; + si->bd_scroll_delay = TRUE; + si->bd_show_invisible_outbox = FALSE; + si->bd_smooth_movements = STATE_AUTO; + si->bd_pushing_graphics = STATE_TRUE; + si->bd_up_down_graphics = STATE_TRUE; + si->bd_skip_falling_sounds = STATE_AUTO; + si->bd_palette_c64 = GD_DEFAULT_PALETTE_C64; + si->bd_palette_c64dtv = GD_DEFAULT_PALETTE_C64DTV; + si->bd_palette_atari = GD_DEFAULT_PALETTE_ATARI; + si->bd_default_color_type = GD_DEFAULT_COLOR_TYPE; + si->bd_random_colors = FALSE; si->sp_show_border_elements = FALSE; si->small_game_graphics = FALSE; si->show_load_save_buttons = FALSE; @@ -10701,13 +11680,14 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); si->music_set = getStringCopy(MUS_CLASSIC_SUBDIR); - si->override_level_graphics = FALSE; - si->override_level_sounds = FALSE; - si->override_level_music = FALSE; + si->override_level_graphics = STATE_FALSE; + si->override_level_sounds = STATE_FALSE; + si->override_level_music = STATE_FALSE; si->volume_simple = 100; // percent si->volume_loops = 100; // percent si->volume_music = 100; // percent + si->audio_sample_rate_44100 = FALSE; si->network_mode = FALSE; si->network_player_nr = 0; // first player @@ -10769,7 +11749,11 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->touch.grid_initialized = video.initialized; + si->touch.overlay_buttons = FALSE; + si->editor.el_boulderdash = TRUE; + si->editor.el_boulderdash_native = TRUE; + si->editor.el_boulderdash_effects = TRUE; si->editor.el_emerald_mine = TRUE; si->editor.el_emerald_mine_club = TRUE; si->editor.el_more = TRUE; @@ -10800,6 +11784,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; + si->shortcut.restart_game = DEFAULT_KEY_RESTART_GAME; + si->shortcut.pause_before_end = DEFAULT_KEY_PAUSE_BEFORE_END; si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; @@ -10824,10 +11810,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.snap_up = DEFAULT_KEY_SNAP_UP; si->shortcut.snap_down = DEFAULT_KEY_SNAP_DOWN; + si->shortcut.speed_fast = DEFAULT_KEY_SPEED_FAST; + si->shortcut.speed_slow = DEFAULT_KEY_SPEED_SLOW; + for (i = 0; i < MAX_PLAYERS; i++) { si->input[i].use_joystick = FALSE; - si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i)); + si->input[i].joy.device_name = getStringCopy(getDeviceNameFromJoystickNr(i)); si->input[i].joy.xleft = JOYSTICK_XLEFT; si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE; si->input[i].joy.xright = JOYSTICK_XRIGHT; @@ -10871,6 +11860,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->internal.choose_from_top_leveldir = FALSE; si->internal.show_scaling_in_title = TRUE; si->internal.create_user_levelset = TRUE; + si->internal.info_screens_from_main = FALSE; si->internal.default_window_width = WIN_XSIZE_DEFAULT; si->internal.default_window_height = WIN_YSIZE_DEFAULT; @@ -10902,13 +11892,16 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->debug.show_frames_per_second = FALSE; - si->debug.xsn_mode = AUTO; + si->debug.xsn_mode = STATE_AUTO; si->debug.xsn_percent = 0; si->options.verbose = FALSE; + si->options.debug = FALSE; + si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING); #if defined(PLATFORM_ANDROID) si->fullscreen = TRUE; + si->touch.overlay_buttons = TRUE; #endif setHideSetupEntry(&setup.debug.xsn_mode); @@ -10937,6 +11930,8 @@ static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si) static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) { si->editor_cascade.el_bd = TRUE; + si->editor_cascade.el_bdx = TRUE; + si->editor_cascade.el_bdx_effects = FALSE; si->editor_cascade.el_em = TRUE; si->editor_cascade.el_emc = TRUE; si->editor_cascade.el_rnd = TRUE; @@ -11232,6 +12227,12 @@ void LoadSetup_Default(void) // try to load setup values from default setup file filename = getDefaultSetupFilename(); + if (fileExists(filename)) + LoadSetupFromFilename(filename); + + // try to load setup values from platform setup file + filename = getPlatformSetupFilename(); + if (fileExists(filename)) LoadSetupFromFilename(filename); @@ -11459,7 +12460,7 @@ void SaveSetup_Default(void) fprintf(file, "\n"); for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++) if (!strPrefix(debug_setup_tokens[i].text, "debug.xsn_") || - setup.debug.xsn_mode != AUTO) + setup.debug.xsn_mode != STATE_AUTO) fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); fprintf(file, "\n"); @@ -11759,8 +12760,9 @@ static boolean string_has_parameter(char *s, char *s_contained) char next_char = s[strlen(s_contained)]; // check if next character is delimiter or whitespace - return (next_char == ',' || next_char == '\0' || - next_char == ' ' || next_char == '\t' ? TRUE : FALSE); + if (next_char == ',' || next_char == '\0' || + next_char == ' ' || next_char == '\t') + return TRUE; } // check if string contains another parameter string after a comma @@ -11778,6 +12780,85 @@ static boolean string_has_parameter(char *s, char *s_contained) return string_has_parameter(substring, s_contained); } +static int get_anim_parameter_value_ce(char *s) +{ + char *s_ptr = s; + char *pattern_1 = "ce_change:custom_"; + char *pattern_2 = ".page_"; + int pattern_1_len = strlen(pattern_1); + char *matching_char = strstr(s_ptr, pattern_1); + int result = ANIM_EVENT_NONE; + + if (matching_char == NULL) + return ANIM_EVENT_NONE; + + result = ANIM_EVENT_CE_CHANGE; + + s_ptr = matching_char + pattern_1_len; + + // check for custom element number ("custom_X", "custom_XX" or "custom_XXX") + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_ce_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + } + + if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS) + return ANIM_EVENT_NONE; + + // custom element stored as 0 to 255 + gic_ce_nr--; + + result |= gic_ce_nr << ANIM_EVENT_CE_BIT; + } + else + { + // invalid custom element number specified + + return ANIM_EVENT_NONE; + } + + // check for change page number ("page_X" or "page_XX") (optional) + if (strPrefix(s_ptr, pattern_2)) + { + s_ptr += strlen(pattern_2); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_page_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0'); + + if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES) + return ANIM_EVENT_NONE; + + // change page stored as 1 to 32 (0 means "all change pages") + + result |= gic_page_nr << ANIM_EVENT_PAGE_BIT; + } + else + { + // invalid animation part number specified + + return ANIM_EVENT_NONE; + } + } + + // discard result if next character is neither delimiter nor whitespace + if (!(*s_ptr == ',' || *s_ptr == '\0' || + *s_ptr == ' ' || *s_ptr == '\t')) + return ANIM_EVENT_NONE; + + return result; +} + static int get_anim_parameter_value(char *s) { int event_value[] = @@ -11803,6 +12884,11 @@ static int get_anim_parameter_value(char *s) int result = ANIM_EVENT_NONE; int i; + result = get_anim_parameter_value_ce(s); + + if (result != ANIM_EVENT_NONE) + return result; + for (i = 0; i < ARRAY_SIZE(event_value); i++) { matching_char = strstr(s_ptr, pattern_1[i]); @@ -11932,6 +13018,18 @@ static int get_anim_action_parameter_value(char *token) result = -(int)key; } + if (result == -1) + { + if (isURL(token)) + { + result = get_hash_from_string(token); // unsigned int => int + result = ABS(result); // may be negative now + result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0); + + setHashEntry(anim_url_hash, int2str(result, 0), token); + } + } + if (result == -1) result = ANIM_EVENT_ACTION_NONE; @@ -11960,6 +13058,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type) strEqual(value, "lower") ? POS_LOWER : strEqual(value, "bottom") ? POS_BOTTOM : strEqual(value, "any") ? POS_ANY : + strEqual(value, "ce") ? POS_CE : + strEqual(value, "ce_trigger") ? POS_CE_TRIGGER : strEqual(value, "last") ? POS_LAST : POS_UNDEFINED); } else if (strEqual(suffix, ".align")) @@ -11993,6 +13093,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "centered") ? ANIM_CENTERED : string_has_parameter(value, "all") ? ANIM_ALL : string_has_parameter(value, "tiled") ? ANIM_TILED : + string_has_parameter(value, "level_nr") ? ANIM_LEVEL_NR : ANIM_DEFAULT); if (string_has_parameter(value, "once")) @@ -12023,7 +13124,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) else if (strEqual(suffix, ".class")) { result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE : - get_hash_from_key(value)); + get_hash_from_string(value)); } else if (strEqual(suffix, ".style")) { @@ -12049,11 +13150,16 @@ int get_parameter_value(char *value_raw, char *suffix, int type) if (string_has_parameter(value, "multiple_actions")) result |= STYLE_MULTIPLE_ACTIONS; + + if (string_has_parameter(value, "consume_ce_event")) + result |= STYLE_CONSUME_CE_EVENT; } else if (strEqual(suffix, ".fade_mode")) { result = (string_has_parameter(value, "none") ? FADE_MODE_NONE : string_has_parameter(value, "fade") ? FADE_MODE_FADE : + string_has_parameter(value, "fade_in") ? FADE_MODE_FADE_IN : + string_has_parameter(value, "fade_out") ? FADE_MODE_FADE_OUT : string_has_parameter(value, "crossfade") ? FADE_MODE_CROSSFADE : string_has_parameter(value, "melt") ? FADE_MODE_MELT : string_has_parameter(value, "curtain") ? FADE_MODE_CURTAIN : @@ -12100,14 +13206,18 @@ static int get_token_parameter_value(char *token, char *value_raw) return get_parameter_value(value_raw, suffix, TYPE_INTEGER); } -void InitMenuDesignSettings_Static(void) +void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash, + boolean ignore_defaults) { int i; - // always start with reliable default values from static default config for (i = 0; image_config_vars[i].token != NULL; i++) { - char *value = getHashEntry(image_config_hash, image_config_vars[i].token); + char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + + // (ignore definitions set to "[DEFAULT]" which are already initialized) + if (ignore_defaults && strEqual(value, ARG_DEFAULT)) + continue; if (value != NULL) *image_config_vars[i].value = @@ -12115,6 +13225,12 @@ void InitMenuDesignSettings_Static(void) } } +void InitMenuDesignSettings_Static(void) +{ + // always start with reliable default values from static default config + InitMenuDesignSettings_FromHash(image_config_hash, FALSE); +} + static void InitMenuDesignSettings_SpecialPreProcessing(void) { int i; @@ -12320,7 +13436,7 @@ static void InitMenuDesignSettings_SpecialPostProcessing(void) vp_playfield->width = MIN(vp_playfield->width, vp_playfield->max_width); if (vp_playfield->max_height != -1) - vp_playfield->height = MIN(vp_playfield->height,vp_playfield->max_height); + vp_playfield->height = MIN(vp_playfield->height, vp_playfield->max_height); // adjust playfield position according to specified alignment @@ -12555,6 +13671,45 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void) } } +static void InitMenuDesignSettings_PreviewPlayers_Ext(SetupFileHash *hash, + boolean initialize) +{ + // special case: check if network and preview player positions are redefined, + // to compare this later against the main menu level preview being redefined + struct TokenIntPtrInfo menu_config_players[] = + { + { "main.network_players.x", &menu.main.network_players.redefined }, + { "main.network_players.y", &menu.main.network_players.redefined }, + { "main.preview_players.x", &menu.main.preview_players.redefined }, + { "main.preview_players.y", &menu.main.preview_players.redefined }, + { "preview.x", &preview.redefined }, + { "preview.y", &preview.redefined } + }; + int i; + + if (initialize) + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + *menu_config_players[i].value = FALSE; + } + else + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + if (getHashEntry(hash, menu_config_players[i].token) != NULL) + *menu_config_players[i].value = TRUE; + } +} + +static void InitMenuDesignSettings_PreviewPlayers(void) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(NULL, TRUE); +} + +static void InitMenuDesignSettings_PreviewPlayers_FromHash(SetupFileHash *hash) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(hash, FALSE); +} + static void LoadMenuDesignSettingsFromFilename(char *filename) { static struct TitleFadingInfo tfi; @@ -12689,7 +13844,9 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { { "menu.draw_xoffset.INFO", &menu.draw_xoffset_info[i] }, { "menu.draw_yoffset.INFO", &menu.draw_yoffset_info[i] }, - { "menu.list_size.INFO", &menu.list_size_info[i] } + { "menu.list_size.INFO", &menu.list_size_info[i] }, + { "menu.list_entry_size.INFO", &menu.list_entry_size_info[i] }, + { "menu.tile_size.INFO", &menu.tile_size_info[i] } }; for (j = 0; j < ARRAY_SIZE(menu_config); j++) @@ -12729,6 +13886,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) struct TokenIntPtrInfo menu_config[] = { { "menu.left_spacing.INFO", &menu.left_spacing_info[i] }, + { "menu.middle_spacing.INFO", &menu.middle_spacing_info[i] }, { "menu.right_spacing.INFO", &menu.right_spacing_info[i] }, { "menu.top_spacing.INFO", &menu.top_spacing_info[i] }, { "menu.bottom_spacing.INFO", &menu.bottom_spacing_info[i] }, @@ -12893,35 +14051,11 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) } } - // special case: check if network and preview player positions are redefined, - // to compare this later against the main menu level preview being redefined - struct TokenIntPtrInfo menu_config_players[] = - { - { "main.network_players.x", &menu.main.network_players.redefined }, - { "main.network_players.y", &menu.main.network_players.redefined }, - { "main.preview_players.x", &menu.main.preview_players.redefined }, - { "main.preview_players.y", &menu.main.preview_players.redefined }, - { "preview.x", &preview.redefined }, - { "preview.y", &preview.redefined } - }; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - *menu_config_players[i].value = FALSE; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - if (getHashEntry(setup_file_hash, menu_config_players[i].token) != NULL) - *menu_config_players[i].value = TRUE; - // read (and overwrite with) values that may be specified in config file - for (i = 0; image_config_vars[i].token != NULL; i++) - { - char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + InitMenuDesignSettings_FromHash(setup_file_hash, TRUE); - // (ignore definitions set to "[DEFAULT]" which are already initialized) - if (value != NULL && !strEqual(value, ARG_DEFAULT)) - *image_config_vars[i].value = - get_token_parameter_value(image_config_vars[i].token, value); - } + // special case: check if network and preview player positions are redefined + InitMenuDesignSettings_PreviewPlayers_FromHash(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -12932,6 +14066,7 @@ void LoadMenuDesignSettings(void) InitMenuDesignSettings_Static(); InitMenuDesignSettings_SpecialPreProcessing(); + InitMenuDesignSettings_PreviewPlayers(); if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS)) { @@ -12955,6 +14090,65 @@ void LoadMenuDesignSettings_AfterGraphics(void) InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(); } +void InitSoundSettings_FromHash(SetupFileHash *setup_file_hash, + boolean ignore_defaults) +{ + int i; + + for (i = 0; sound_config_vars[i].token != NULL; i++) + { + char *value = getHashEntry(setup_file_hash, sound_config_vars[i].token); + + // (ignore definitions set to "[DEFAULT]" which are already initialized) + if (ignore_defaults && strEqual(value, ARG_DEFAULT)) + continue; + + if (value != NULL) + *sound_config_vars[i].value = + get_token_parameter_value(sound_config_vars[i].token, value); + } +} + +void InitSoundSettings_Static(void) +{ + // always start with reliable default values from static default config + InitSoundSettings_FromHash(sound_config_hash, FALSE); +} + +static void LoadSoundSettingsFromFilename(char *filename) +{ + SetupFileHash *setup_file_hash; + + if ((setup_file_hash = loadSetupFileHash(filename)) == NULL) + return; + + // read (and overwrite with) values that may be specified in config file + InitSoundSettings_FromHash(setup_file_hash, TRUE); + + freeSetupFileHash(setup_file_hash); +} + +void LoadSoundSettings(void) +{ + char *filename_base = UNDEFINED_FILENAME, *filename_local; + + InitSoundSettings_Static(); + + if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_SOUNDS)) + { + // first look for special settings configured in level series config + filename_base = getCustomArtworkLevelConfigFilename(ARTWORK_TYPE_SOUNDS); + + if (fileExists(filename_base)) + LoadSoundSettingsFromFilename(filename_base); + } + + filename_local = getCustomArtworkConfigFilename(ARTWORK_TYPE_SOUNDS); + + if (filename_local != NULL && !strEqual(filename_base, filename_local)) + LoadSoundSettingsFromFilename(filename_local); +} + void LoadUserDefinedEditorElementList(int **elements, int *num_elements) { char *filename = getEditorSetupFilename(); @@ -13054,11 +14248,13 @@ static struct MusicFileInfo *get_music_file_info_ext(char *basename, int music, { "artist_header", &tmp_music_file_info.artist_header }, { "album_header", &tmp_music_file_info.album_header }, { "year_header", &tmp_music_file_info.year_header }, + { "played_header", &tmp_music_file_info.played_header }, { "title", &tmp_music_file_info.title }, { "artist", &tmp_music_file_info.artist }, { "album", &tmp_music_file_info.album }, { "year", &tmp_music_file_info.year }, + { "played", &tmp_music_file_info.played }, { NULL, NULL }, }; @@ -13154,14 +14350,12 @@ static boolean sound_info_listed(struct MusicFileInfo *list, char *basename) void LoadMusicInfo(void) { - char *music_directory = getCustomMusicDirectory(); + int num_music_noconf = getMusicListSize_NoConf(); int num_music = getMusicListSize(); - int num_music_noconf = 0; int num_sounds = getSoundListSize(); - Directory *dir; - DirectoryEntry *dir_entry; struct FileInfo *music, *sound; struct MusicFileInfo *next, **new; + int i; while (music_file_info != NULL) @@ -13174,11 +14368,13 @@ void LoadMusicInfo(void) checked_free(music_file_info->artist_header); checked_free(music_file_info->album_header); checked_free(music_file_info->year_header); + checked_free(music_file_info->played_header); checked_free(music_file_info->title); checked_free(music_file_info->artist); checked_free(music_file_info->album); checked_free(music_file_info->year); + checked_free(music_file_info->played); free(music_file_info); @@ -13187,76 +14383,68 @@ void LoadMusicInfo(void) new = &music_file_info; - for (i = 0; i < num_music; i++) + // get (configured or unconfigured) music file info for all levels + for (i = leveldir_current->first_level; + i <= leveldir_current->last_level; i++) { - music = getMusicListEntry(i); + int music_nr; - if (music->filename == NULL) - continue; + if (levelset.music[i] != MUS_UNDEFINED) + { + // get music file info for configured level music + music_nr = levelset.music[i]; + } + else if (num_music_noconf > 0) + { + // get music file info for unconfigured level music + int level_pos = i - leveldir_current->first_level; - if (strEqual(music->filename, UNDEFINED_FILENAME)) + music_nr = MAP_NOCONF_MUSIC(level_pos % num_music_noconf); + } + else + { continue; + } - // a configured file may be not recognized as music - if (!FileIsMusic(music->filename)) + char *basename = getMusicInfoEntryFilename(music_nr); + + if (basename == NULL) continue; - if (!music_info_listed(music_file_info, music->filename)) + if (!music_info_listed(music_file_info, basename)) { - *new = get_music_file_info(music->filename, i); + *new = get_music_file_info(basename, music_nr); if (*new != NULL) new = &(*new)->next; } } - if ((dir = openDirectory(music_directory)) == NULL) - { - Warn("cannot read music directory '%s'", music_directory); - - return; - } - - while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries + // get music file info for all remaining configured music files + for (i = 0; i < num_music; i++) { - char *basename = dir_entry->basename; - boolean music_already_used = FALSE; - int i; - - // skip all music files that are configured in music config file - for (i = 0; i < num_music; i++) - { - music = getMusicListEntry(i); - - if (music->filename == NULL) - continue; + music = getMusicListEntry(i); - if (strEqual(basename, music->filename)) - { - music_already_used = TRUE; - break; - } - } + if (music->filename == NULL) + continue; - if (music_already_used) + if (strEqual(music->filename, UNDEFINED_FILENAME)) continue; - if (!FileIsMusic(dir_entry->filename)) + // a configured file may be not recognized as music + if (!FileIsMusic(music->filename)) continue; - if (!music_info_listed(music_file_info, basename)) + if (!music_info_listed(music_file_info, music->filename)) { - *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf)); + *new = get_music_file_info(music->filename, i); if (*new != NULL) new = &(*new)->next; } - - num_music_noconf++; } - closeDirectory(dir); - + // get sound file info for all configured sound files for (i = 0; i < num_sounds; i++) { sound = getSoundListEntry(i); @@ -13278,6 +14466,18 @@ void LoadMusicInfo(void) new = &(*new)->next; } } + + // add pointers to previous list nodes + + struct MusicFileInfo *node = music_file_info; + + while (node != NULL) + { + if (node->next) + node->next->prev = node; + + node = node->next; + } } static void add_helpanim_entry(int element, int action, int direction, @@ -13780,8 +14980,25 @@ void CreateCollectElementImages(void) int dst_width = anim_width * 2; int dst_height = anim_height * num_collect_images / 2; Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH); - char *basename = "RocksCollect.bmp"; - char *filename = getPath2(global.create_collect_images_dir, basename); + char *basename_bmp = "RocksCollect.bmp"; + char *basename_png = "RocksCollect.png"; + char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp); + char *filename_png = getPath2(global.create_collect_images_dir, basename_png); + int len_filename_bmp = strlen(filename_bmp); + int len_filename_png = strlen(filename_png); + int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png; + char cmd_convert[max_command_len]; + + snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"", + filename_bmp, + filename_png); + + // force using RGBA surface for destination bitmap + SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00)); + + dst_bitmap->surface = + SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); for (i = 0; i < MAX_NUM_ELEMENTS; i++) { @@ -13803,6 +15020,13 @@ void CreateCollectElementImages(void) BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y, tile_size, tile_size, 0, 0); + // force using RGBA surface for temporary bitmap (using transparent black) + SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00)); + + tmp_bitmap->surface = + SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + tmp_bitmap->surface_masked = tmp_bitmap->surface; for (j = 0; j < anim_frames; j++) @@ -13823,9 +15047,9 @@ void CreateCollectElementImages(void) frame_bitmap = half_bitmap; } - BlitBitmap(frame_bitmap, dst_bitmap, 0, 0, - frame_size_final, frame_size_final, - dst_x + j * tile_size + offset, dst_y + offset); + BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0, + frame_size_final, frame_size_final, + dst_x + j * tile_size + offset, dst_y + offset); FreeBitmap(frame_bitmap); } @@ -13837,11 +15061,18 @@ void CreateCollectElementImages(void) pos_collect_images++; } - if (SDL_SaveBMP(dst_bitmap->surface, filename) != 0) - Fail("cannot save element collecting image file '%s'", filename); + if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0) + Fail("cannot save element collecting image file '%s'", filename_bmp); FreeBitmap(dst_bitmap); + Info("Converting image file from BMP to PNG ..."); + + if (system(cmd_convert) != 0) + Fail("converting image file failed"); + + unlink(filename_bmp); + Info("Done."); CloseAllAndExit(0);