X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=ce6928b7c43514c9bdb8a191107703652a630a6b;hb=b197ad86c0801f6139e4f147bf22bd375c9dfb63;hp=0b0c37d340981bac3395684ad0d48a4c0262fe19;hpb=12a8fd3a64d6bee5ca5f5b89e4a00b49d78bbd2c;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index 0b0c37d3..ce6928b7 100644 --- a/src/files.c +++ b/src/files.c @@ -4,7 +4,7 @@ // (c) 1995-2014 by Artsoft Entertainment // Holger Schemel // info@artsoft.org -// http://www.artsoft.org/ +// https://www.artsoft.org/ // ---------------------------------------------------------------------------- // 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,9 @@ #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 2 // 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 #define LEVEL_CHUNK_CNT3_SIZE(x) (LEVEL_CHUNK_CNT3_HEADER + (x)) #define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE) @@ -67,7 +72,7 @@ // file identifier strings #define LEVEL_COOKIE_TMPL "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x" #define TAPE_COOKIE_TMPL "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x" -#define SCORE_COOKIE "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2" +#define SCORE_COOKIE_TMPL "ROCKSNDIAMONDS_SCORE_FILE_VERSION_x.x" // values for deciding when (not) to save configuration data #define SAVE_CONF_NEVER 0 @@ -110,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 + \ @@ -161,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), @@ -172,90 +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, @@ -307,6 +397,16 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = TYPE_BOOLEAN, CONF_VALUE_8_BIT(15), &li.lazy_relocation, FALSE }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), + &li.finish_dig_collect, TRUE + }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(17), + &li.keep_walkable_ce, FALSE + }, // (these values are different for each player) { @@ -537,740 +637,887 @@ 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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_SAND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_sand_looks_like, EL_BD_SAND }, { - EL_KEY_1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_KEY], 10 + EL_BD_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_rock_turns_to_on_falling, EL_BD_ROCK_FALLING }, - { - EL_PEARL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_PEARL], 10 + EL_BD_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_rock_turns_to_on_impact, EL_BD_ROCK }, { - EL_CRYSTAL, -1, + EL_BD_DIAMOND, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_CRYSTAL], 10 + &li.score[SC_DIAMOND_EXTRA], 20 + }, + { + EL_BD_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_diamond_turns_to_on_falling, EL_BD_DIAMOND_FALLING + }, + { + EL_BD_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_diamond_turns_to_on_impact, EL_BD_DIAMOND }, { - EL_BD_AMOEBA, -1, + EL_BD_FIREFLY, -1, TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &li.amoeba_content, EL_DIAMOND + &li.bd_firefly_explodes_to, EL_BD_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.amoeba_speed, 10 + EL_BD_FIREFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_firefly_2_explodes_to, EL_BD_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.grow_into_diggable, TRUE + EL_BD_BUTTERFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_explodes_to, EL_BD_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_BD_BUTTERFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_2_explodes_to, EL_BD_DIAMOND_GROWING_1 }, + { - EL_YAMYAM, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_YAMYAM], 10 + EL_BD_STONEFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_stonefly_explodes_to, EL_BD_ROCK_GROWING_1 }, { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_ROBOT], 10 + EL_BD_DRAGONFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_dragonfly_explodes_to, EL_BD_EXPLODING_1 }, + { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.slurp_score, 10 + EL_BD_DIAMOND_GROWING_5, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_diamond_birth_turns_to, EL_BD_DIAMOND }, { - EL_ROBOT_WHEEL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_wheel, 10 + EL_BD_BOMB_EXPLODING_4, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bomb_explosion_turns_to, EL_BD_WALL }, { - EL_MAGIC_WALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_magic_wall, 10 + EL_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_magic_wall_diamond_to, EL_BD_ROCK_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &li.biomaze[1], 3 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_magic_wall_rock_to, EL_BD_DIAMOND_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(3), - &li.biomaze[2], 3 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_magic_wall_mega_rock_to, EL_BD_NITRO_PACK_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(4), - &li.biomaze[3], 3 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_magic_wall_nut_to, EL_BD_NUT_FALLING }, - { - EL_TIMEGATE_SWITCH, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_timegate, 10 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_magic_wall_nitro_pack_to, EL_BD_MEGA_ROCK_FALLING }, - { - EL_LIGHT_SWITCH_ACTIVE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_light, 10 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_magic_wall_flying_diamond_to, EL_BD_FLYING_ROCK_FLYING }, - { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.shield_normal_time, 10 + EL_BD_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &li.bd_magic_wall_flying_rock_to, EL_BD_FLYING_DIAMOND_FLYING }, + { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BD_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(1), - &li.shield_deadly_time, 10 + EL_BD_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_voodoo_collects_diamonds, FALSE }, { - EL_SHIELD_DEADLY, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BD_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(1), - &li.extra_time, 10 + EL_BD_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_voodoo_dies_by_rock, FALSE }, { - EL_EXTRA_TIME, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.extra_time_score, 10 + EL_BD_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_voodoo_vanish_by_explosion, TRUE }, - { - EL_TIME_ORB_FULL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_orb_time, 10 + EL_BD_VOODOO_DOLL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &li.bd_voodoo_penalty_time, 30 }, + { - EL_TIME_ORB_FULL, -1, + EL_BD_SLIME, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_time_orb_bug, FALSE + &li.bd_slime_is_predictable, TRUE }, - { - EL_SPRING, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_spring_bug, FALSE + EL_BD_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(1), - &li.android_move_time, 10 + EL_BD_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_slime_permeability_bits_c64, 0 }, { - EL_EMC_ANDROID, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.android_clone_time, 10 + EL_BD_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(1), - &li.android_clone_element[0], EL_EMPTY, NULL, - &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_slime_eats_element_1, EL_BD_DIAMOND + }, + { + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_slime_converts_to_element_1, EL_BD_DIAMOND_FALLING + }, + { + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_slime_eats_element_2, EL_BD_ROCK + }, + { + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_slime_converts_to_element_2, EL_BD_ROCK_FALLING + }, + { + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_slime_eats_element_3, EL_BD_NUT + }, + { + EL_BD_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_slime_converts_to_element_3, EL_BD_NUT_FALLING }, { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.lenses_score, 10 + EL_BD_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_acid_eats_element, EL_BD_SAND }, { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.lenses_time, 10 + EL_BD_ACID, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_acid_spread_rate, 3 + }, + { + EL_BD_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_acid_turns_to_element, EL_BD_EXPLODING_3 }, { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.magnify_score, 10 + EL_BD_BITER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_biter_move_delay, 0 }, { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.magnify_time, 10 + EL_BD_BITER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_biter_eats_element, EL_BD_DIAMOND }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.ball_time, 10 + EL_BD_BLADDER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bladder_converts_by_element, EL_BD_VOODOO_DOLL }, + { - EL_EMC_MAGIC_BALL, -1, + EL_BD_EXPANDABLE_WALL_ANY, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.ball_random, FALSE + &li.bd_change_expanding_wall, FALSE }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.ball_state_initial, FALSE + EL_BD_EXPANDABLE_WALL_ANY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_expanding_wall_looks_like, EL_BD_WALL }, + { - 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_BD_REPLICATOR, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_replicators_active, TRUE + }, + { + EL_BD_REPLICATOR, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_replicator_create_delay, 4 }, { - EL_MM_MCDUFFIN, -1, + EL_BD_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_BD_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_BD_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_BD_NUT, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_nut_content, EL_BD_NUT_BREAKING_1 }, + { - EL_DF_LASER, -1, + EL_BD_PNEUMATIC_HAMMER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_hammer_walls_break_delay, 5 + }, + { + EL_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_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_BD_GRAVITY_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_gravity_switch_delay, 10 }, { - -1, -1, + EL_BD_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 }, + // (amoeba values used by R'n'D game engine only) { - -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 }, - + // (amoeba values used by BD game engine only) { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(6), - &xx_ei.push_delay_fixed, 8, - &yy_ei.push_delay_fixed + EL_BD_AMOEBA, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_amoeba_wait_for_hatching, FALSE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(7), - &xx_ei.push_delay_random, 8, - &yy_ei.push_delay_random + EL_BD_AMOEBA, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_amoeba_start_immediately, TRUE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(8), - &xx_ei.drop_delay_fixed, 0, - &yy_ei.drop_delay_fixed + EL_BD_AMOEBA, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_amoeba_2_explode_by_amoeba, TRUE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(9), - &xx_ei.drop_delay_random, 0, - &yy_ei.drop_delay_random + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_threshold_too_big, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(10), - &xx_ei.move_delay_fixed, 0, - &yy_ei.move_delay_fixed + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_slow_growth_time, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(11), - &xx_ei.move_delay_random, 0, - &yy_ei.move_delay_random + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &li.bd_amoeba_slow_growth_rate, 3 }, - { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_ei.move_pattern, MV_ALL_DIRECTIONS, - &yy_ei.move_pattern + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(6), + &li.bd_amoeba_fast_growth_rate, 25 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(4), - &xx_ei.move_direction_initial, MV_START_AUTOMATIC, - &yy_ei.move_direction_initial + EL_BD_AMOEBA, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_amoeba_content_too_big, EL_BD_ROCK }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(5), - &xx_ei.move_stepsize, TILEX / 8, - &yy_ei.move_stepsize + EL_BD_AMOEBA, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_amoeba_content_enclosed, EL_BD_DIAMOND }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(12), - &xx_ei.move_enter_element, EL_EMPTY_SPACE, - &yy_ei.move_enter_element + EL_BD_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_2_threshold_too_big, 200 }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(13), - &xx_ei.move_leave_element, EL_EMPTY_SPACE, - &yy_ei.move_leave_element + EL_BD_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_2_slow_growth_time, 200 }, { - -1, -1, + EL_BD_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &li.bd_amoeba_2_slow_growth_rate, 3 + }, + { + EL_BD_AMOEBA_2, -1, TYPE_INTEGER, CONF_VALUE_8_BIT(6), - &xx_ei.move_leave_type, LEAVE_TYPE_UNLIMITED, - &yy_ei.move_leave_type + &li.bd_amoeba_2_fast_growth_rate, 25 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(7), - &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, - &yy_ei.slippery_type + EL_BD_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_amoeba_2_content_too_big, EL_BD_ROCK }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(8), - &xx_ei.explosion_type, EXPLODES_3X3, - &yy_ei.explosion_type + EL_BD_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_amoeba_2_content_enclosed, EL_BD_DIAMOND }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(14), - &xx_ei.explosion_delay, 16, - &yy_ei.explosion_delay + EL_BD_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &li.bd_amoeba_2_content_exploding, EL_EMPTY }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(15), - &xx_ei.ignition_delay, 8, - &yy_ei.ignition_delay + EL_BD_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(8), + &li.bd_amoeba_2_content_looks_like, EL_BD_AMOEBA_2 }, { - -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_CONTENT_LIST, CONF_VALUE_BYTES(1), + &li.yamyam_content, EL_ROCK, NULL, + &li.num_yamyam_contents, 4, MAX_ELEMENT_CONTENTS }, - - // ---------- "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_YAMYAM, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_YAMYAM], 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(1), + &li.score[SC_ROBOT], 10 + }, { - -1, SAVE_CONF_ALWAYS, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &xx_current_change_page, -1 + EL_ROBOT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.slurp_score, 10 }, - // ---------- (the remaining entries can be in any order) ------------------- - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &xx_change.can_change, FALSE + EL_ROBOT_WHEEL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_wheel, 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(1), - &xx_event_bits[0], 0 + EL_MAGIC_WALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_magic_wall, 10 }, + { - -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(1), + &li.game_of_life[0], 2 }, - { - -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(2), + &li.game_of_life[1], 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(3), + &li.game_of_life[2], 3 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_change.trigger_page, CH_PAGE_ANY + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.game_of_life[3], 3 }, - { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_change.target_element, EL_EMPTY_SPACE + EL_GAME_OF_LIFE, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), + &li.use_life_bugs, FALSE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &xx_change.delay_fixed, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.biomaze[0], 2 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(3), - &xx_change.delay_random, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.biomaze[1], 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(3), + &li.biomaze[2], 3 }, - { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(5), - &xx_change.initial_trigger_element, EL_EMPTY_SPACE + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.biomaze[3], 3 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), - &xx_change.explode, FALSE + EL_TIMEGATE_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_timegate, 10 }, + { - -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, + 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), - &xx_change.target_content, EL_EMPTY_SPACE, NULL, - &xx_num_contents, 1, 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 }, { @@ -1280,38 +1527,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] }, { @@ -1321,433 +1566,893 @@ 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, + TYPE_INTEGER, CONF_VALUE_16_BIT(4), + &xx_ei.ce_value_fixed_initial, 0, + &yy_ei.ce_value_fixed_initial + }, + { -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 }, -}; - + 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 + }, -// ============================================================================ -// level file functions -// ============================================================================ + { + -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 + }, -static boolean check_special_flags(char *flag) -{ - if (strEqual(options.special_flags, flag) || - strEqual(leveldir_current->special_flags, flag)) - return TRUE; + { + -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 + }, - return FALSE; -} + { + -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 + }, -static struct DateInfo getCurrentDate(void) -{ - time_t epoch_seconds = time(NULL); - struct tm *now = localtime(&epoch_seconds); - struct DateInfo date; + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(7), + &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, + &yy_ei.slippery_type + }, - date.year = now->tm_year + 1900; - date.month = now->tm_mon + 1; - date.day = now->tm_mday; + { + -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 + }, - date.src = DATE_SRC_CLOCK; + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(2), + &xx_ei.content, EL_EMPTY_SPACE, + &yy_ei.content, + &xx_num_contents, 1, 1 + }, - return date; -} + // ---------- "num_change_pages" must be the last entry --------------------- -static void resetEventFlags(struct ElementChangeInfo *change) -{ - int i; + { + -1, SAVE_CONF_ALWAYS, + TYPE_INTEGER, CONF_VALUE_8_BIT(9), + &xx_ei.num_change_pages, 1, + &yy_ei.num_change_pages + }, - for (i = 0; i < NUM_CHANGE_EVENTS; i++) - change->has_event[i] = FALSE; -} + { + -1, -1, + -1, -1, + NULL, -1, + NULL + } +}; -static void resetEventBits(void) +static struct LevelFileConfigInfo chunk_config_CUSX_change[] = { - int i; - - for (i = 0; i < NUM_CE_BITFIELDS; i++) - xx_event_bits[i] = 0; -} + // ---------- "current_change_page" must be the first entry ----------------- -static void setEventFlagsFromEventBits(struct ElementChangeInfo *change) -{ - int i; + { + -1, SAVE_CONF_ALWAYS, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &xx_current_change_page, -1 + }, - /* 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)) */ + // ---------- (the remaining entries can be in any order) ------------------- - 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; -} + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &xx_change.can_change, FALSE + }, -static void setEventBitsFromEventFlags(struct ElementChangeInfo *change) -{ - int i; + { + -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 + }, - /* 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 */ + { + -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 + }, - 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); -} + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_change.target_element, EL_EMPTY_SPACE + }, -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; + -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 + }, - 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; + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &xx_change.initial_trigger_element, EL_EMPTY_SPACE + }, - *(int *)(conf[i].num_entities) = default_num_entities; + { + -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 + }, - if (data_type == TYPE_STRING) - { - char *default_string = conf[i].default_string; - char *string = (char *)(conf[i].value); + { + -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 + }, - strncpy(string, default_string, max_num_entities); - } - else if (data_type == TYPE_ELEMENT_LIST) - { - int *element_array = (int *)(conf[i].value); - int j; + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &xx_change.action_element, EL_EMPTY_SPACE + }, - 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; + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &xx_change.target_content, EL_EMPTY_SPACE, NULL, + &xx_num_contents, 1, 1 + }, - 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; - } + { + -1, -1, + -1, -1, + NULL, -1 } -} +}; -static void copyConfigFromConfigList(struct LevelFileConfigInfo *conf) +static struct LevelFileConfigInfo chunk_config_GRPX[] = { - 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; + -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] + }, - if (data_type == TYPE_STRING) - { - char *string = (char *)(conf[i].value); - char *string_copy = (char *)(conf[i].value_copy); + { + -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 + }, - 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; + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &xx_group.choice_mode, ANIM_RANDOM + }, - 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; + { + -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 + }, - 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); - } + { + -1, -1, + -1, -1, + NULL, -1 } -} +}; -void copyElementInfo(struct ElementInfo *ei_from, struct ElementInfo *ei_to) +static struct LevelFileConfigInfo chunk_config_EMPX[] = { - 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); + { + -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 + }, - *ei_from = xx_ei; - *ei_to = yy_ei; + { + -1, -1, + -1, -1, + NULL, -1 + } +}; - // ---------- 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]; +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 + }, - // ---------- copy group element info ---------- - if (ei_from->group != NULL && ei_to->group != NULL) // group or internal - *ei_to->group = *ei_from->group; + { + -1, -1, + -1, -1, + NULL, -1 + } +}; - // mark this custom element as modified - ei_to->modified_settings = TRUE; +static struct +{ + int filetype; + char *id; } - -void setElementChangePages(struct ElementInfo *ei, int change_pages) +filetype_id_list[] = { - int change_page_size = sizeof(struct ElementChangeInfo); + { 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 }, +}; - ei->num_change_pages = MAX(1, change_pages); - ei->change_page = - checked_realloc(ei->change_page, ei->num_change_pages * change_page_size); +// ============================================================================ +// level file functions +// ============================================================================ - if (ei->current_change_page >= ei->num_change_pages) - ei->current_change_page = ei->num_change_pages - 1; +static boolean check_special_flags(char *flag) +{ + if (strEqual(options.special_flags, flag) || + strEqual(leveldir_current->special_flags, flag)) + return TRUE; - ei->change = &ei->change_page[ei->current_change_page]; + return FALSE; } -void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) +static struct DateInfo getCurrentDate(void) { - xx_change = *change; // copy change data into temporary buffer + time_t epoch_seconds = time(NULL); + struct tm *now = localtime(&epoch_seconds); + struct DateInfo date; - setConfigToDefaultsFromConfigList(chunk_config_CUSX_change); + date.year = now->tm_year + 1900; + date.month = now->tm_mon + 1; + date.day = now->tm_mday; - *change = xx_change; + date.src = DATE_SRC_CLOCK; - resetEventFlags(change); + return date; +} - change->direct_action = 0; - change->other_action = 0; +static void resetEventFlags(struct ElementChangeInfo *change) +{ + int i; - change->pre_change_function = NULL; - change->change_function = NULL; - change->post_change_function = NULL; + for (i = 0; i < NUM_CHANGE_EVENTS; i++) + change->has_event[i] = FALSE; } -static void setLevelInfoToDefaults_Level(struct LevelInfo *level) +static void resetEventBits(void) { - int i, x, y; + int i; - li = *level; // copy level data into temporary buffer - setConfigToDefaultsFromConfigList(chunk_config_INFO); - *level = li; // copy temporary buffer back to level data + for (i = 0; i < NUM_CE_BITFIELDS; i++) + xx_event_bits[i] = 0; +} - setLevelInfoToDefaults_EM(); - setLevelInfoToDefaults_SP(); - setLevelInfoToDefaults_MM(); +static void setEventFlagsFromEventBits(struct ElementChangeInfo *change) +{ + int i; - level->native_em_level = &native_em_level; - level->native_sp_level = &native_sp_level; - level->native_mm_level = &native_mm_level; + /* 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)) */ - level->file_version = FILE_VERSION_ACTUAL; - level->game_version = GAME_VERSION_ACTUAL; + 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; +} - level->creation_date = getCurrentDate(); +static void setEventBitsFromEventFlags(struct ElementChangeInfo *change) +{ + int i; - level->encoding_16bit_field = TRUE; - level->encoding_16bit_yamyam = TRUE; - level->encoding_16bit_amoeba = TRUE; + /* 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 */ - // 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'; + 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); +} - // set level name and level author to default values - strcpy(level->name, NAMELESS_LEVEL_NAME); - strcpy(level->author, ANONYMOUS_NAME); +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; - // 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; + // 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); - level->field[0][0] = EL_PLAYER_1; - level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED; + return &description[0]; +} - BorderElement = EL_STEELWALL; +static void setElementDescriptionToDefault(struct ElementInfo *ei) +{ + char *default_description = getDefaultElementDescription(ei); + int i; - // detect custom elements when loading them - level->file_has_custom_elements = FALSE; + for (i = 0; i < MAX_ELEMENT_NAME_LEN + 1; i++) + ei->description[i] = default_description[i]; +} - // set all bug compatibility flags to "false" => do not emulate this bug - level->use_action_after_change_bug = FALSE; +static void setConfigToDefaultsFromConfigList(struct LevelFileConfigInfo *conf) +{ + int i; - if (leveldir_current) + for (i = 0; conf[i].data_type != -1; i++) { - // 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 + 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) { - switch (LEVELCLASS(leveldir_current)) - { - case LEVELCLASS_TUTORIAL: - strcpy(level->author, PROGRAM_AUTHOR_STRING); - break; + int default_num_entities = conf[i].default_num_entities; + int max_num_entities = conf[i].max_num_entities; - case LEVELCLASS_CONTRIB: - strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN); - level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; - break; + *(int *)(conf[i].num_entities) = default_num_entities; - case LEVELCLASS_PRIVATE: - strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN); - level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; - break; + if (data_type == TYPE_STRING) + { + char *default_string = conf[i].default_string; + char *string = (char *)(conf[i].value); - default: - // keep default value - break; + 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) +{ + 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; + + li = *level; // copy level data into temporary buffer + setConfigToDefaultsFromConfigList(chunk_config_INFO); + *level = li; // copy temporary buffer back to level data + + setLevelInfoToDefaults_BD(); + setLevelInfoToDefaults_EM(); + setLevelInfoToDefaults_SP(); + setLevelInfoToDefaults_MM(); + + 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; + + 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 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) + { + add_border = TRUE; + x1++; + y1++; + x2--; + y2--; + } + + // 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); + } + } + + level->field[x1][y1] = getEngineElement(EL_PLAYER_1); + level->field[x2][y2] = getEngineElement(EL_EXIT_CLOSED); + + BorderElement = getEngineElement(EL_STEELWALL); + + // detect custom elements when loading them + level->file_has_custom_elements = FALSE; + + // set random colors for BD style levels according to preferred color type + SetRandomLevelColors_BD(setup.bd_default_color_type); + + // set default color type and colors for BD style level colors + SetDefaultLevelColorType_BD(); + SetDefaultLevelColors_BD(); + + // 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) { @@ -1765,6 +2470,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) int element = i; struct ElementInfo *ei = &element_info[element]; + if (element == EL_MM_GRAY_BALL) + { + struct LevelInfo_MM *level_mm = level->native_mm_level; + int j; + + 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]); + } + // 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) @@ -1794,8 +2509,7 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) setElementChangeInfoToDefaults(ei->change); if (IS_CUSTOM_ELEMENT(element) || - IS_GROUP_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) + IS_GROUP_ELEMENT(element)) { setElementDescriptionToDefault(ei); @@ -1838,6 +2552,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) *group = xx_group; } + + if (IS_EMPTY_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + xx_ei = *ei; // copy element data into temporary buffer + + setConfigToDefaultsFromConfigList(chunk_config_EMPX); + + *ei = xx_ei; + } } clipboard_elements_initialized = TRUE; @@ -1947,6 +2671,27 @@ static void ActivateLevelTemplate(void) } } +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; + + // 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; + + // this is a level package in native BD file format + return TRUE; +} + static char *getLevelFilenameFromBasename(char *basename) { static char *filename = NULL; @@ -1981,6 +2726,10 @@ static int getFileTypeFromBasename(char *basename) strchr(basename, '%') == NULL) return LEVEL_FILE_TYPE_SB; + // check for typical filename of a Boulder Dash (GDash) level package file + if (checkForPackageFromBasename_BD(basename)) + return LEVEL_FILE_TYPE_BD; + // ---------- try to determine file type from filesize ---------- checked_free(filename); @@ -2052,7 +2801,7 @@ static char *getPackedLevelBasename(int type) if ((dir = openDirectory(directory)) == NULL) { - Error(ERR_WARN, "cannot read current level directory '%s'", directory); + Warn("cannot read current level directory '%s'", directory); return basename; } @@ -2240,6 +2989,11 @@ static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi) 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); @@ -2316,7 +3070,7 @@ static void copyLevelFileInfo(struct LevelFileInfo *lfi_from, // functions for loading R'n'D level // ---------------------------------------------------------------------------- -static int getMappedElement(int element) +int getMappedElement(int element) { // remap some (historic, now obsolete) elements @@ -2357,7 +3111,7 @@ static int getMappedElement(int element) default: if (element >= NUM_FILE_ELEMENTS) { - Error(ERR_WARN, "invalid level element %d", element); + Warn("invalid level element %d", element); element = EL_UNKNOWN; } @@ -2605,7 +3359,7 @@ static int LoadLevel_CNT2(File *file, int chunk_size, struct LevelInfo *level) } else { - Error(ERR_WARN, "cannot load content for element '%d'", element); + Warn("cannot load content for element '%d'", element); } return chunk_size; @@ -2665,7 +3419,7 @@ static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level) if (IS_CUSTOM_ELEMENT(element)) element_info[element].properties[EP_BITFIELD_BASE_NR] = properties; else - Error(ERR_WARN, "invalid custom element number %d", element); + 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) @@ -2698,7 +3452,7 @@ static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level) if (IS_CUSTOM_ELEMENT(element)) element_info[element].change->target_element = custom_target_element; else - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); } level->file_has_custom_elements = TRUE; @@ -2726,7 +3480,7 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) if (!IS_CUSTOM_ELEMENT(element)) { - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); element = EL_INTERNAL_DUMMY; } @@ -2759,9 +3513,10 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) for (x = 0; x < 3; x++) ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); + // bits 0 - 31 of "has_event[]" event_bits = getFile32BitBE(file); - for (j = 0; j < NUM_CHANGE_EVENTS; j++) - if (event_bits & (1 << j)) + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) + if (event_bits & (1u << j)) ei->change->has_event[j] = TRUE; ei->change->target_element = getMappedElement(getFile16BitBE(file)); @@ -2770,7 +3525,7 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) ei->change->delay_random = getFile16BitBE(file); ei->change->delay_frames = getFile16BitBE(file); - ei->change->initial_trigger_element= getMappedElement(getFile16BitBE(file)); + ei->change->initial_trigger_element = getMappedElement(getFile16BitBE(file)); ei->change->explode = getFile8Bit(file); ei->change->use_target_content = getFile8Bit(file); @@ -2812,9 +3567,10 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) if (!IS_CUSTOM_ELEMENT(element)) { - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size; } @@ -2896,7 +3652,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) // 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)) + if (event_bits & (1u << j)) change->has_event[j] = TRUE; change->target_element = getMappedElement(getFile16BitBE(file)); @@ -2937,7 +3693,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) // ... 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))) + if (event_bits & (1u << (j - 32))) change->has_event[j] = TRUE; } @@ -2960,9 +3716,10 @@ static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level) if (!IS_GROUP_ELEMENT(element)) { - Error(ERR_WARN, "invalid group element number %d", element); + Warn("invalid group element number %d", element); ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size; } @@ -3024,9 +3781,8 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, if (num_entities > max_num_entities) { - Error(ERR_WARN, - "truncating number of entities for element %d from %d to %d", - element, num_entities, max_num_entities); + Warn("truncating number of entities for element %d from %d to %d", + element, num_entities, max_num_entities); num_entities = max_num_entities; } @@ -3035,8 +3791,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, data_type == TYPE_CONTENT_LIST)) { // for element and content lists, zero entities are not allowed - Error(ERR_WARN, "found empty list of entities for element %d", - element); + Warn("found empty list of entities for element %d", element); // do not set "num_entities" here to prevent reading behind buffer @@ -3105,7 +3860,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, value = getMappedElement(value); if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value) = value; + *(boolean *)(conf[i].value) = (value ? TRUE : FALSE); else *(int *) (conf[i].value) = value; @@ -3127,9 +3882,9 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN; int error_element = real_element; - Error(ERR_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)); + 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; @@ -3246,8 +4001,8 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) if (ei->num_change_pages == -1) { - Error(ERR_WARN, "LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", - EL_NAME(element)); + Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", + EL_NAME(element)); ei->num_change_pages = 1; @@ -3267,6 +4022,10 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) while (!checkEndOfFile(file)) { + // level file might contain invalid change page number + if (xx_current_change_page >= ei->num_change_pages) + break; + struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page]; xx_change = *change; // copy change data into temporary buffer @@ -3296,6 +4055,9 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) struct ElementInfo *ei = &element_info[element]; struct ElementGroupInfo *group = ei->group; + if (group == NULL) + return -1; + xx_ei = *ei; // copy element data into temporary buffer xx_group = *group; // copy group data into temporary buffer @@ -3316,6 +4078,30 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) return real_chunk_size; } +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]; + + xx_ei = *ei; // copy element data into temporary buffer + + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX, + -1, element); + + if (real_chunk_size >= chunk_size) + break; + } + + *ei = xx_ei; + + level->file_has_custom_elements = TRUE; + + return real_chunk_size; +} + static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -3334,7 +4120,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (level_info_only) return; - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); if (!setup.editor.use_template_for_new_levels) return; @@ -3361,7 +4147,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of level file '%s'", filename); + Warn("unknown format of level file '%s'", filename); closeFile(file); @@ -3380,7 +4166,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of level file '%s'", filename); + Warn("unknown format of level file '%s'", filename); closeFile(file); @@ -3391,7 +4177,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unsupported version of level file '%s'", filename); + Warn("unsupported version of level file '%s'", filename); closeFile(file); @@ -3438,6 +4224,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { "NOTE", -1, LoadLevel_NOTE }, { "CUSX", -1, LoadLevel_CUSX }, { "GRPX", -1, LoadLevel_GRPX }, + { "EMPX", -1, LoadLevel_EMPX }, { NULL, 0, NULL } }; @@ -3452,15 +4239,17 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (chunk_info[i].name == NULL) { - Error(ERR_WARN, "unknown chunk '%s' in level file '%s'", - chunk_name, filename); + 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) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); } else @@ -3469,13 +4258,23 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, int chunk_size_expected = (chunk_info[i].loader)(file, chunk_size, level); + if (chunk_size_expected < 0) + { + Warn("error reading chunk '%s' in level file '%s'", + chunk_name, filename); + + break; + } + // 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) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); + + break; } } } @@ -3485,6 +4284,440 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, } +// ---------------------------------------------------------------------------- +// functions for loading BD level +// ---------------------------------------------------------------------------- + +#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 void CopyNativeLevel_RND_to_BD(struct LevelInfo *level) +{ + 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; + + 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->time_magic_wall; + 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_threshold_too_big; + cave->level_amoeba_time[0] = level->bd_amoeba_slow_growth_time; + cave->amoeba_growth_prob = level->bd_amoeba_slow_growth_rate * 10000; + cave->amoeba_fast_growth_prob = level->bd_amoeba_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_content_too_big); + cave->amoeba_enclosed_effect = LEVEL_TO_CAVE(level->bd_amoeba_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_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_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; + + 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->time_magic_wall = 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_threshold_too_big = cave->level_amoeba_threshold[bd_level_nr]; + level->bd_amoeba_slow_growth_time = cave->level_amoeba_time[bd_level_nr]; + level->bd_amoeba_slow_growth_rate = cave->amoeba_growth_prob / 10000; + level->bd_amoeba_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_content_too_big = CAVE_TO_LEVEL(cave->amoeba_too_big_effect); + level->bd_amoeba_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_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_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]); + + checked_free(cave_name); +} + +static void setTapeInfoToDefaults(void); + +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; + + if (replay == NULL) + return; + + // always start with reliable default values + setTapeInfoToDefaults(); + + tape.level_nr = level_nr; // (currently not used) + tape.random_seed = replay->seed; + + TapeSetDateFromIsoDateString(replay->date); + + 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++) + { + 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; + + while (1) + { + success = TapeAddAction(action); + + milliseconds_game += milliseconds_elapsed; + + if (milliseconds_game >= cave_speed) + { + milliseconds_game -= cave_speed; + + break; + } + } + + tape.counter++; + tape.pos[tape.counter].delay = 0; + tape.pos[tape.counter].action[0] = 0; + + if (!success) + { + Warn("BD replay truncated: size exceeds maximum tape size %d", MAX_TAPE_LEN); + + break; + } + } + + TapeHaltRecording(); + + if (!replay->success) + Warn("BD replay is marked as not successful"); +} + + // ---------------------------------------------------------------------------- // functions for loading EM level // ---------------------------------------------------------------------------- @@ -3503,118 +4736,99 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) { 2, 2 }, }; struct LevelInfo_EM *level_em = level->native_em_level; - struct LEVEL *lev = level_em->lev; - struct PLAYER **ply = level_em->ply; + struct CAVE *cav = level_em->cav; int i, j, x, y; - lev->width = MIN(level->fieldx, EM_MAX_CAVE_WIDTH); - lev->height = MIN(level->fieldy, EM_MAX_CAVE_HEIGHT); + cav->width = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH); + cav->height = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT); - lev->time_seconds = level->time; - lev->required_initial = level->gems_needed; + cav->time_seconds = level->time; + cav->gems_needed = level->gems_needed; - lev->emerald_score = level->score[SC_EMERALD]; - lev->diamond_score = level->score[SC_DIAMOND]; - lev->alien_score = level->score[SC_ROBOT]; - lev->tank_score = level->score[SC_SPACESHIP]; - lev->bug_score = level->score[SC_BUG]; - lev->eater_score = level->score[SC_YAMYAM]; - lev->nut_score = level->score[SC_NUT]; - lev->dynamite_score = level->score[SC_DYNAMITE]; - lev->key_score = level->score[SC_KEY]; - lev->exit_score = level->score[SC_TIME_BONUS]; + 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]; + + cav->num_eater_arrays = level->num_yamyam_contents; for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - lev->eater_array[i][y * 3 + x] = - map_element_RND_to_EM(level->yamyam_content[i].e[x][y]); + cav->eater_array[i][y * 3 + x] = + map_element_RND_to_EM_cave(level->yamyam_content[i].e[x][y]); - lev->amoeba_time = level->amoeba_speed; - lev->wonderwall_time_initial = level->time_magic_wall; - lev->wheel_time = level->time_wheel; + cav->amoeba_time = level->amoeba_speed; + cav->wonderwall_time = level->time_magic_wall; + cav->wheel_time = level->time_wheel; - lev->android_move_time = level->android_move_time; - lev->android_clone_time = level->android_clone_time; - lev->ball_random = level->ball_random; - lev->ball_state_initial = level->ball_state_initial; - lev->ball_time = level->ball_time; - lev->num_ball_arrays = level->num_ball_contents; + 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; - lev->lenses_score = level->lenses_score; - lev->magnify_score = level->magnify_score; - lev->slurp_score = level->slurp_score; + cav->lenses_score = level->lenses_score; + cav->magnify_score = level->magnify_score; + cav->slurp_score = level->slurp_score; - lev->lenses_time = level->lenses_time; - lev->magnify_time = level->magnify_time; + cav->lenses_time = level->lenses_time; + cav->magnify_time = level->magnify_time; - lev->wind_direction_initial = + cav->wind_time = 9999; + cav->wind_direction = map_direction_RND_to_EM(level->wind_direction_initial); - lev->wind_cnt_initial = (level->wind_direction_initial != MV_NONE ? - lev->wind_time : 0); for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (j = 0; j < 8; j++) - lev->ball_array[i][j] = - map_element_RND_to_EM(level-> - ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]]); + cav->ball_array[i][j] = + map_element_RND_to_EM_cave(level->ball_content[i]. + e[ball_xy[j][0]][ball_xy[j][1]]); map_android_clone_elements_RND_to_EM(level); - // first fill the complete playfield with the default border element + // 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++) - level_em->cave[x][y] = ZBORDER; - - if (BorderElement == EL_STEELWALL) - { - for (y = 0; y < lev->height + 2; y++) - for (x = 0; x < lev->width + 2; x++) - level_em->cave[x + 1][y + 1] = map_element_RND_to_EM(EL_STEELWALL); - } + cav->cave[x][y] = Cblank; // then copy the real level contents from level file into the playfield - for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++) + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { - int new_element = map_element_RND_to_EM(level->field[x][y]); - int offset = (BorderElement == EL_STEELWALL ? 1 : 0); - int xx = x + 1 + offset; - int yy = y + 1 + offset; + 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(EL_AMOEBA_WET); + new_element = map_element_RND_to_EM_cave(EL_AMOEBA_WET); - level_em->cave[xx][yy] = new_element; + cav->cave[x][y] = new_element; } for (i = 0; i < MAX_PLAYERS; i++) { - ply[i]->x_initial = 0; - ply[i]->y_initial = 0; + cav->player_x[i] = -1; + cav->player_y[i] = -1; } // initialize player positions and delete players from the playfield - for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++) + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { - if (ELEM_IS_PLAYER(level->field[x][y])) + if (IS_PLAYER_ELEMENT(level->field[x][y])) { int player_nr = GET_PLAYER_NR(level->field[x][y]); - int offset = (BorderElement == EL_STEELWALL ? 1 : 0); - int xx = x + 1 + offset; - int yy = y + 1 + offset; - ply[player_nr]->x_initial = xx; - ply[player_nr]->y_initial = yy; + cav->player_x[player_nr] = x; + cav->player_y[player_nr] = y; - level_em->cave[xx][yy] = map_element_RND_to_EM(EL_EMPTY); + cav->cave[x][y] = map_element_RND_to_EM_cave(EL_EMPTY); } } - - if (BorderElement == EL_STEELWALL) - { - lev->width += 2; - lev->height += 2; - } } static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) @@ -3631,69 +4845,68 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) { 2, 2 }, }; struct LevelInfo_EM *level_em = level->native_em_level; - struct LEVEL *lev = level_em->lev; - struct PLAYER **ply = level_em->ply; + struct CAVE *cav = level_em->cav; int i, j, x, y; - level->fieldx = MIN(lev->width, MAX_LEV_FIELDX); - level->fieldy = MIN(lev->height, MAX_LEV_FIELDY); + level->fieldx = MIN(cav->width, MAX_LEV_FIELDX); + level->fieldy = MIN(cav->height, MAX_LEV_FIELDY); - level->time = lev->time_seconds; - level->gems_needed = lev->required_initial; + level->time = cav->time_seconds; + level->gems_needed = cav->gems_needed; sprintf(level->name, "Level %d", level->file_info.nr); - level->score[SC_EMERALD] = lev->emerald_score; - level->score[SC_DIAMOND] = lev->diamond_score; - level->score[SC_ROBOT] = lev->alien_score; - level->score[SC_SPACESHIP] = lev->tank_score; - level->score[SC_BUG] = lev->bug_score; - level->score[SC_YAMYAM] = lev->eater_score; - level->score[SC_NUT] = lev->nut_score; - level->score[SC_DYNAMITE] = lev->dynamite_score; - level->score[SC_KEY] = lev->key_score; - level->score[SC_TIME_BONUS] = lev->exit_score; + 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; - level->num_yamyam_contents = MAX_ELEMENT_CONTENTS; + level->num_yamyam_contents = cav->num_eater_arrays; - for (i = 0; i < level->num_yamyam_contents; i++) + 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(lev->eater_array[i][y * 3 + x]); + map_element_EM_to_RND_cave(cav->eater_array[i][y * 3 + x]); - level->amoeba_speed = lev->amoeba_time; - level->time_magic_wall = lev->wonderwall_time_initial; - level->time_wheel = lev->wheel_time; + level->amoeba_speed = cav->amoeba_time; + level->time_magic_wall = cav->wonderwall_time; + level->time_wheel = cav->wheel_time; - level->android_move_time = lev->android_move_time; - level->android_clone_time = lev->android_clone_time; - level->ball_random = lev->ball_random; - level->ball_state_initial = lev->ball_state_initial; - level->ball_time = lev->ball_time; - level->num_ball_contents = lev->num_ball_arrays; + 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; - level->lenses_score = lev->lenses_score; - level->magnify_score = lev->magnify_score; - level->slurp_score = lev->slurp_score; + level->lenses_score = cav->lenses_score; + level->magnify_score = cav->magnify_score; + level->slurp_score = cav->slurp_score; - level->lenses_time = lev->lenses_time; - level->magnify_time = lev->magnify_time; + level->lenses_time = cav->lenses_time; + level->magnify_time = cav->magnify_time; level->wind_direction_initial = - map_direction_EM_to_RND(lev->wind_direction_initial); + map_direction_EM_to_RND(cav->wind_direction); 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(lev->ball_array[i][j]); + map_element_EM_to_RND_cave(cav->ball_array[i][j]); map_android_clone_elements_EM_to_RND(level); // 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(level_em->cave[x + 1][y + 1]); + int new_element = map_element_EM_to_RND_cave(cav->cave[x][y]); if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0) new_element = EL_AMOEBA_DEAD; @@ -3705,12 +4918,15 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) { // in case of all players set to the same field, use the first player int nr = MAX_PLAYERS - i - 1; - int jx = ply[nr]->x_initial - 1; - int jy = ply[nr]->y_initial - 1; + int jx = cav->player_x[nr]; + int jy = cav->player_y[nr]; if (jx != -1 && jy != -1) level->field[jx][jy] = EL_PLAYER_1 + nr; } + + // time score is counted for each 10 seconds left in Emerald Mine levels + level->time_score_base = 10; } @@ -3823,7 +5039,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) { num_invalid_elements++; - Error(ERR_DEBUG, "invalid element %d at position %d, %d", + Debug("level:native:SP", "invalid element %d at position %d, %d", element_old, x, y); } @@ -3832,8 +5048,8 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) } if (num_invalid_elements > 0) - Error(ERR_WARN, "found %d invalid elements%s", num_invalid_elements, - (!options.debug ? " (use '--debug' for more details)" : "")); + Warn("found %d invalid elements%s", num_invalid_elements, + (!options.debug ? " (use '--debug' for more details)" : "")); for (i = 0; i < MAX_PLAYERS; i++) level->initial_player_gravity[i] = @@ -3869,8 +5085,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) if (port_x < 0 || port_x >= level->fieldx || port_y < 0 || port_y >= level->fieldy) { - Error(ERR_WARN, "special port position (%d, %d) out of bounds", - port_x, port_y); + Warn("special port position (%d, %d) out of bounds", port_x, port_y); continue; } @@ -3880,7 +5095,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) if (port_element < EL_SP_GRAVITY_PORT_RIGHT || port_element > EL_SP_GRAVITY_PORT_UP) { - Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y); + Warn("no special port at position (%d, %d)", port_x, port_y); continue; } @@ -3905,12 +5120,11 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) level->time_wheel = 0; level->amoeba_content = EL_EMPTY; -#if 1 - // original Supaplex does not use score values -- use default values -#else + // original Supaplex does not use score values -- rate by playing time for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) level->score[i] = 0; -#endif + + level->rate_time_over_score = TRUE; // there are no yamyams in supaplex levels for (i = 0; i < level->num_yamyam_contents; i++) @@ -3946,8 +5160,8 @@ static void CopyNativeTape_RND_to_SP(struct LevelInfo *level) if (demo->length + demo_entries >= SP_MAX_TAPE_LEN) { - Error(ERR_WARN, "tape truncated: size exceeds maximum SP demo size %d", - SP_MAX_TAPE_LEN); + Warn("tape truncated: size exceeds maximum SP demo size %d", + SP_MAX_TAPE_LEN); break; } @@ -3962,8 +5176,6 @@ static void CopyNativeTape_RND_to_SP(struct LevelInfo *level) demo->is_available = TRUE; } -static void setTapeInfoToDefaults(void); - static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) { struct LevelInfo_SP *level_sp = level->native_sp_level; @@ -3991,7 +5203,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) 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_PLAYERS] = { tape_action, 0, 0, 0 }; + byte action[MAX_TAPE_ACTIONS] = { tape_action }; boolean success = 0; int j; @@ -4000,8 +5212,8 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) if (!success) { - Error(ERR_WARN, "SP demo truncated: size exceeds maximum tape size %d", - MAX_TAPE_LEN); + Warn("SP demo truncated: size exceeds maximum tape size %d", + MAX_TAPE_LEN); break; } @@ -4018,7 +5230,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) { struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + int i, x, y; level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH); level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT); @@ -4027,9 +5239,13 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) level_mm->kettles_needed = level->gems_needed; level_mm->auto_count_kettles = level->auto_count_gems; - level_mm->laser_red = level->mm_laser_red; - level_mm->laser_green = level->mm_laser_green; - level_mm->laser_blue = level->mm_laser_blue; + 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; + + 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; strcpy(level_mm->name, level->name); strcpy(level_mm->author, level->author); @@ -4046,6 +5262,15 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) level_mm->time_ball = level->mm_time_ball; level_mm->time_block = level->mm_time_block; + 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; + + 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]); + for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++) Ur[x][y] = @@ -4055,7 +5280,7 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) { struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + int i, x, y; level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX); level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY); @@ -4064,9 +5289,13 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) level->gems_needed = level_mm->kettles_needed; level->auto_count_gems = level_mm->auto_count_kettles; - level->mm_laser_red = level_mm->laser_red; - level->mm_laser_green = level_mm->laser_green; - level->mm_laser_blue = level_mm->laser_blue; + 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; + + 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; strcpy(level->name, level_mm->name); @@ -4086,6 +5315,15 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) level->mm_time_ball = level_mm->time_ball; level->mm_time_block = level_mm->time_block; + 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; + + 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]); + 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]); @@ -4276,7 +5514,7 @@ static int getMappedElement_DC(int element) break; case 0x13f5: - element = EL_YAMYAM; + element = EL_YAMYAM_UP; break; case 0x1425: @@ -5302,7 +6540,7 @@ static int getMappedElement_DC(int element) break; case 0x1682: // secret gate (red) - element = EL_GATE_1_GRAY; + element = EL_EM_GATE_1_GRAY; break; case 0x1683: // gate (yellow) @@ -5310,7 +6548,7 @@ static int getMappedElement_DC(int element) break; case 0x1684: // secret gate (yellow) - element = EL_GATE_2_GRAY; + element = EL_EM_GATE_2_GRAY; break; case 0x1685: // gate (blue) @@ -5318,7 +6556,7 @@ static int getMappedElement_DC(int element) break; case 0x1686: // secret gate (blue) - element = EL_GATE_4_GRAY; + element = EL_EM_GATE_4_GRAY; break; case 0x1687: // gate (green) @@ -5326,7 +6564,7 @@ static int getMappedElement_DC(int element) break; case 0x1688: // secret gate (green) - element = EL_GATE_3_GRAY; + element = EL_EM_GATE_3_GRAY; break; case 0x1689: // gate (white) @@ -5557,7 +6795,8 @@ static int getMappedElement_DC(int element) element = EL_INVISIBLE_SAND; else { - Error(ERR_WARN, "unknown Diamond Caves element 0x%04x", element); + Warn("unknown Diamond Caves element 0x%04x", element); + element = EL_UNKNOWN; } break; @@ -5566,8 +6805,7 @@ static int getMappedElement_DC(int element) return getMappedElement(element); } -static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, - int nr) +static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level) { byte header[DC_LEVEL_HEADER_SIZE]; int envelope_size; @@ -5605,7 +6843,7 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot decode level from stream -- using empty level"); + Warn("cannot decode level from stream -- using empty level"); return; } @@ -5718,9 +6956,19 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, level->extra_time = header[56] | (header[57] << 8); level->shield_normal_time = header[58] | (header[59] << 8); + // shield and extra time elements do not have a score + level->score[SC_SHIELD] = 0; + level->extra_time_score = 0; + + // set time for normal and deadly shields to the same value + level->shield_deadly_time = level->shield_normal_time; + // 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; + + // time score is counted for each 10 seconds left in Diamond Caves levels + level->time_score_base = 10; } static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, @@ -5738,7 +6986,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, level->no_valid_file = TRUE; if (!level_info_only) - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -5756,8 +7004,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown DC level file '%s' -- using empty level", - filename); + Warn("unknown DC level file '%s' -- using empty level", filename); return; } @@ -5783,8 +7030,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", - filename); + Warn("cannot fseek in file '%s' -- using empty level", filename); return; } @@ -5802,14 +7048,13 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown DC2 level file '%s' -- using empty level", - filename); + Warn("unknown DC2 level file '%s' -- using empty level", filename); return; } } - LoadLevelFromFileStream_DC(file, level, level_file_info->nr); + LoadLevelFromFileStream_DC(file, level); closeFile(file); } @@ -5850,6 +7095,21 @@ int getMappedElement_SB(int element_ascii, boolean use_ces) return EL_UNDEFINED; } +static void SetLevelSettings_SB(struct LevelInfo *level) +{ + // time settings + level->time = 0; + level->use_step_counter = TRUE; + + // score settings + level->score[SC_TIME_BONUS] = 0; + level->time_score_base = 1; + level->rate_time_over_score = TRUE; + + // game settings + level->auto_exit_sokoban = TRUE; +} + static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -5867,7 +7127,6 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, 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 last_comment[0] = '\0'; @@ -5878,7 +7137,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, level->no_valid_file = TRUE; if (!level_info_only) - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -5909,10 +7168,6 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, if (!getStringFromFile(file, line, MAX_LINE_LEN)) break; - // check if line was completely read and is terminated by line break - if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') - line_nr++; - // 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') @@ -6052,7 +7307,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -6083,14 +7338,11 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, } // set special level settings for Sokoban levels - - level->time = 0; - level->use_step_counter = TRUE; + SetLevelSettings_SB(level); if (load_xsb_to_ces) { // special global settings can now be set in level template - level->use_custom_template = TRUE; } } @@ -6100,6 +7352,20 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, // functions for handling native levels // ------------------------------------------------------------------------- +static void LoadLevelFromFileInfo_BD(struct LevelInfo *level, + struct LevelFileInfo *level_file_info, + boolean level_info_only) +{ + int pos = 0; + + // determine position of requested level inside level package + if (level_file_info->packed) + pos = level_file_info->nr - leveldir_current->first_level; + + if (!LoadNativeLevel_BD(level_file_info->filename, pos, level_info_only)) + level->no_valid_file = TRUE; +} + static void LoadLevelFromFileInfo_EM(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -6132,7 +7398,9 @@ static void LoadLevelFromFileInfo_MM(struct LevelInfo *level, void CopyNativeLevel_RND_to_Native(struct LevelInfo *level) { - if (level->game_engine_type == GAME_ENGINE_TYPE_EM) + 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); @@ -6142,7 +7410,9 @@ void CopyNativeLevel_RND_to_Native(struct LevelInfo *level) void CopyNativeLevel_Native_to_RND(struct LevelInfo *level) { - if (level->game_engine_type == GAME_ENGINE_TYPE_EM) + 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); @@ -6152,16 +7422,40 @@ void CopyNativeLevel_Native_to_RND(struct LevelInfo *level) void SaveNativeLevel(struct LevelInfo *level) { - if (level->game_engine_type == GAME_ENGINE_TYPE_SP) + // 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; + + 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); + + if (fileExists(filename) && !Request("Native level file already exists! Overwrite it?", REQ_ASK)) + return; + + boolean success = FALSE; + + if (level->game_engine_type == GAME_ENGINE_TYPE_BD) { - char *basename = getSingleLevelBasenameExt(level->file_info.nr, "sp"); - char *filename = getLevelFilenameFromBasename(basename); + CopyNativeLevel_RND_to_BD(level); + // CopyNativeTape_RND_to_BD(level); + success = SaveNativeLevel_BD(filename); + } + else if (level->game_engine_type == GAME_ENGINE_TYPE_SP) + { CopyNativeLevel_RND_to_SP(level); CopyNativeTape_RND_to_SP(level); - SaveNativeLevel_SP(filename); + success = SaveNativeLevel_SP(filename); } + + if (success) + Request("Native level file saved!", REQ_CONFIRM); + else + Request("Failed to save native level file!", REQ_CONFIRM); } @@ -6182,6 +7476,11 @@ static void LoadLevelFromFileInfo(struct LevelInfo *level, LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only); break; + case LEVEL_FILE_TYPE_BD: + LoadLevelFromFileInfo_BD(level, level_file_info, level_info_only); + level->game_engine_type = GAME_ENGINE_TYPE_BD; + break; + case LEVEL_FILE_TYPE_EM: LoadLevelFromFileInfo_EM(level, level_file_info, level_info_only); level->game_engine_type = GAME_ENGINE_TYPE_EM; @@ -6214,6 +7513,9 @@ static void LoadLevelFromFileInfo(struct LevelInfo *level, if (level->no_valid_file) setLevelInfoToDefaults(level, level_info_only, FALSE); + if (check_special_flags("use_native_bd_game_engine")) + level->game_engine_type = GAME_ENGINE_TYPE_BD; + if (level->game_engine_type == GAME_ENGINE_TYPE_UNKNOWN) level->game_engine_type = GAME_ENGINE_TYPE_RND; @@ -6247,7 +7549,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) 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->score[SC_TIME_BONUS] /= 10; + level->time_score_base = 10; } if (leveldir_current->latest_engine) @@ -6314,10 +7616,6 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) level->extra_time_score = level->score[SC_TIME_BONUS]; } - // 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; - if (level->game_version < VERSION_IDENT(3,2,0,7)) { // default behaviour for snapping was "not continuous" before 3.2.0-7 @@ -6415,6 +7713,53 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) // 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; + + // 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; + + // 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; + + // 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; + + // 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; +} + +static void LoadLevel_InitSettings_SB(struct LevelInfo *level) +{ + 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; + + if (is_sokoban_level) + { + // set special level settings for Sokoban levels + SetLevelSettings_SB(level); + } +} + +static void LoadLevel_InitSettings(struct LevelInfo *level) +{ + // adjust level settings for (non-native) Sokoban-style levels + LoadLevel_InitSettings_SB(level); + + // 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); } static void LoadLevel_InitStandardElements(struct LevelInfo *level) @@ -6562,6 +7907,27 @@ static void LoadLevel_InitCustomElements(struct LevelInfo *level) element_info[element].ignition_delay = 8; } } + + // 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]; + + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; + + 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; + } + } + } } static void LoadLevel_InitElements(struct LevelInfo *level) @@ -6595,7 +7961,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level) // copy elements to runtime playfield array for (x = 0; x < MAX_LEV_FIELDX; x++) for (y = 0; y < MAX_LEV_FIELDY; y++) - Feld[x][y] = level->field[x][y]; + Tile[x][y] = level->field[x][y]; // initialize level size variables for faster access lev_fieldx = level->fieldx; @@ -6622,6 +7988,7 @@ static void LoadLevelTemplate_LoadAndInit(void) LoadLevel_InitVersion(&level_template); LoadLevel_InitElements(&level_template); + LoadLevel_InitSettings(&level_template); ActivateLevelTemplate(); } @@ -6630,7 +7997,7 @@ void LoadLevelTemplate(int nr) { if (!fileExists(getGlobalLevelTemplateFilename())) { - Error(ERR_WARN, "no level template found for this level"); + Warn("no level template found for this level"); return; } @@ -6662,6 +8029,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level) LoadLevel_InitVersion(&level); LoadLevel_InitElements(&level); LoadLevel_InitPlayfield(&level); + LoadLevel_InitSettings(&level); LoadLevel_InitNativeEngines(&level); } @@ -6793,8 +8161,8 @@ static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) int chunk_size = 0; int x, y; - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) + 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 @@ -6871,7 +8239,8 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) // chunk header already written -- write empty chunk data WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE); - Error(ERR_WARN, "cannot save content for element '%d'", element); + Warn("cannot save content for element '%d'", element); + return; } @@ -6939,7 +8308,7 @@ static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom element properties"); + Warn("inconsistent number of custom element properties"); } #endif @@ -6968,7 +8337,7 @@ static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom target elements"); + Warn("inconsistent number of custom target elements"); } #endif @@ -7051,7 +8420,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom element properties"); + Warn("inconsistent number of custom element properties"); } #endif @@ -7129,7 +8498,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) event_bits = 0; for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) if (change->has_event[j]) - event_bits |= (1 << j); + event_bits |= (1u << j); putFile32BitBE(file, event_bits); putFile16BitBE(file, change->target_element); @@ -7169,7 +8538,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) event_bits = 0; for (j = 32; j < NUM_CHANGE_EVENTS; j++) if (change->has_event[j]) - event_bits |= (1 << (j - 32)); + event_bits |= (1u << (j - 32)); putFile8Bit(file, event_bits); } } @@ -7420,6 +8789,22 @@ static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element) return chunk_size; } +static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element) +{ + struct ElementInfo *ei = &element_info[element]; + int chunk_size = 0; + int i; + + chunk_size += putFile16BitBE(file, element); + + xx_ei = *ei; // copy element data into temporary buffer + + for (i = 0; chunk_config_EMPX[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE); + + return chunk_size; +} + static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, boolean save_as_template) { @@ -7429,7 +8814,8 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot save level file '%s'", filename); + Warn("cannot save level file '%s'", filename); + return; } @@ -7510,6 +8896,18 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, SaveLevel_GRPX(file, level, element); } } + + for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++) + { + int element = GET_EMPTY_ELEMENT(i); + + 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); + } + } } fclose(file); @@ -7554,7 +8952,7 @@ void DumpLevel(struct LevelInfo *level) { if (level->no_level_file || level->no_valid_file) { - Error(ERR_WARN, "cannot dump -- no valid level file found"); + Warn("cannot dump -- no valid level file found"); return; } @@ -7585,10 +8983,55 @@ void DumpLevel(struct LevelInfo *level) 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")); + + if (options.debug) + { + int i, j; + + for (i = 0; i < NUM_ENVELOPES; i++) + { + char *text = level->envelope[i].text; + int text_len = strlen(text); + boolean has_text = FALSE; + + 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); + } + } + } PrintLine("-", 79); } +void DumpLevels(void) +{ + 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); + + 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); + + leveldir_current = dumplevel_leveldir; + + LoadLevel(global.dumplevel_level_nr); + DumpLevel(&level); + + CloseAllAndExit(0); +} + // ============================================================================ // tape file functions @@ -7609,24 +9052,67 @@ static void setTapeInfoToDefaults(void) // 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 LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape) +static int getTapePosSize(struct TapeInfo *tape) { - tape->file_version = getFileVersion(file); - tape->game_version = getFileVersion(file); + int tape_pos_size = 0; - return chunk_size; -} + 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 + + tape_pos_size += 1; // tape action delay value + + return tape_pos_size; +} + +static void setTapeActionFlags(struct TapeInfo *tape, int value) +{ + 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 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); +} + +static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape) +{ + tape->file_version = getFileVersion(file); + tape->game_version = getFileVersion(file); + + return chunk_size; +} static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) { @@ -7655,9 +9141,10 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) } } - tape->use_mouse = (getFile8Bit(file) == 1 ? TRUE : FALSE); + setTapeActionFlags(tape, getFile8Bit(file)); - ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED); + tape->property_bits = getFile8Bit(file); + tape->solved = getFile8Bit(file); engine_version = getFileVersion(file); if (engine_version > 0) @@ -7669,18 +9156,33 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) return chunk_size; } +static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape) +{ + tape->scr_fieldx = getFile8Bit(file); + tape->scr_fieldy = getFile8Bit(file); + + return chunk_size; +} + 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); - tape->level_identifier = - checked_realloc(tape->level_identifier, level_identifier_size); + level_identifier = checked_malloc(level_identifier_size); for (i = 0; i < level_identifier_size; i++) - tape->level_identifier[i] = getFile8Bit(file); + level_identifier[i] = getFile8Bit(file); + + strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN); + tape->level_identifier[MAX_FILENAME_LEN] = '\0'; + + checked_free(level_identifier); tape->level_nr = getFile16BitBE(file); @@ -7692,8 +9194,7 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) { int i, j; - int tape_pos_size = - (tape->use_mouse ? 3 : tape->num_participating_players) + 1; + int tape_pos_size = getTapePosSize(tape); int chunk_size_expected = tape_pos_size * tape->length; if (chunk_size_expected != chunk_size) @@ -7706,25 +9207,17 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) { if (i >= MAX_TAPE_LEN) { - Error(ERR_WARN, "tape truncated -- size exceeds maximum tape size %d", + 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->num_participating_players + 1); + ReadUnusedBytesFromFile(file, tape_pos_size); break; } - if (tape->use_mouse) - { - 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].action[TAPE_ACTION_UNUSED] = 0; - } - else + if (tape->use_key_actions) { for (j = 0; j < MAX_PLAYERS; j++) { @@ -7735,6 +9228,13 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) } } + 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) @@ -7746,7 +9246,7 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) byte action = tape->pos[i].action[0]; int k, num_moves = 0; - for (k = 0; k<4; k++) + for (k = 0; k < 4; k++) { if (action & joy_dir[k]) { @@ -7853,7 +9353,7 @@ static void LoadTape_SokobanSolution(char *filename) default: tape.no_valid_file = TRUE; - Error(ERR_WARN, "unsupported Sokoban solution file '%s' ['%d']", filename, c); + Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c); break; } @@ -7880,1809 +9380,3783 @@ void LoadTapeFromFilename(char *filename) if (strSuffix(filename, ".sln")) { - LoadTape_SokobanSolution(filename); - - return; - } - - if (!(file = openFile(filename, MODE_READ))) + 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")) + { + getFile32BitBE(file); // not used + + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "TAPE")) + { + tape.no_valid_file = TRUE; + + Warn("unknown format of tape 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'; + + if (!checkCookieString(cookie, TAPE_COOKIE_TMPL)) + { + tape.no_valid_file = TRUE; + + Warn("unknown format of tape file '%s'", filename); + + closeFile(file); + + return; + } + + 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 (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); + } + else + { + 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); + + 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]); + + putFile16BitBE(file, tape->level_nr); +} + +static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) +{ + int i, j; + + 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]); + } + + 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]); + } + + putFile8Bit(file, tape->pos[i].delay); + } +} + +void SaveTapeToFilename(char *filename) +{ + 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); + + return; + } + + tape_pos_size = getTapePosSize(&tape); + + 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); +} + +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) +{ + char *filename = getTapeFilename(nr); + + InitTapeDirectory(leveldir_current->subdir); + + SaveTapeExt(filename); +} + +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 boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved, + unsigned int req_state_added) +{ + char *filename = getTapeFilename(nr); + boolean new_tape = !fileExists(filename); + boolean tape_saved = FALSE; + + if (new_tape || Request(msg_replace, REQ_ASK | req_state_added)) + { + SaveTape(nr); + + if (new_tape) + Request(msg_saved, REQ_CONFIRM | req_state_added); + + tape_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); +} + +void DumpTape(struct TapeInfo *tape) +{ + int tape_frame_counter; + int i, j; + + if (tape->no_valid_file) + { + Warn("cannot dump -- no valid tape file found"); + + return; + } + + 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("Solution tape: %s\n", + tape->solved ? "yes" : + tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no"); + + 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"); + + 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; + + for (i = 0; i < tape->length; i++) + { + if (i >= MAX_TAPE_LEN) + break; + + 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' : ' ')); + } + } + + Print("(%03d) ", tape->pos[i].delay); + Print("[%05d]\n", tape_frame_counter); + + tape_frame_counter += tape->pos[i].delay; + } + + PrintLine("-", 79); +} + +void DumpTapes(void) +{ + static LevelDirTree *dumptape_leveldir = NULL; + + dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumptape_leveldir); + + if (dumptape_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumptape_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); + + leveldir_current = dumptape_leveldir; + + if (options.mytapes) + LoadTape(global.dumptape_level_nr); + else + LoadSolutionTape(global.dumptape_level_nr); + + DumpTape(&tape); + + CloseAllAndExit(0); +} + + +// ============================================================================ +// score file functions +// ============================================================================ + +static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) +{ + 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; + + 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, "??"); + } + + 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 +} + +static void setScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&scores); +} + +static void setServerScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&server_scores); +} + +static void LoadScore_OLD(int nr) +{ + 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'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + fclose(file); + + return; + } + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + 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'; + + 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; + } + } + } + + 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; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + int score = scores.entry[i].score; + + 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); + + return chunk_size; +} + +static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) +{ + char *level_identifier = NULL; + int level_identifier_size; + int i; + + level_identifier_size = getFile16BitBE(file); + + level_identifier = checked_malloc(level_identifier_size); + + 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'; + + checked_free(level_identifier); + + scores->level_nr = getFile16BitBE(file); + scores->num_entries = getFile16BitBE(file); + + chunk_size = 2 + level_identifier_size + 2 + 2; + + return chunk_size; +} + +static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + scores->entry[i].name[j] = getFile8Bit(file); + + scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN; + + return chunk_size; +} + +static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile16BitBE(file); + + chunk_size = scores->num_entries * 2; + + return chunk_size; +} + +static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + +static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].time = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + +static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + 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); + + scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + return chunk_size; +} + +void LoadScore(int nr) +{ + 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; + + // always start with reliable default values + setScoreInfoToDefaults(); + + if (!(file = openFile(filename, MODE_READ))) + return; + + getFileChunkBE(file, chunk_name, NULL); + if (strEqual(chunk_name, "RND1")) + { + getFile32BitBE(file); // not used + + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "SCOR")) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + } + else // check for old 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'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + + old_score_file_format = TRUE; + } + + if (old_score_file_format) + { + // 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(); + } + else + { + static struct + { + 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 }, + + { 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 score 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 score file '%s'", + chunk_size, chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this score chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &scores); + + // 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); + } + } + } + } + + closeFile(file); +} + +#if ENABLE_HISTORIC_CHUNKS +void SaveScore_OLD(int nr) +{ + int i; + char *filename = getScoreFilename(nr); + FILE *file; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score for level %d", nr); + + return; + } + + fprintf(file, "%s\n\n", SCORE_COOKIE); + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} +#endif + +static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores) +{ + putFileVersion(file, scores->file_version); + putFileVersion(file, scores->game_version); +} + +static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores) +{ + int level_identifier_size = strlen(scores->level_identifier) + 1; + int i; + + putFile16BitBE(file, level_identifier_size); + + for (i = 0; i < level_identifier_size; i++) + putFile8Bit(file, scores->level_identifier[i]); + + putFile16BitBE(file, scores->level_nr); + putFile16BitBE(file, scores->num_entries); +} + +static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores) +{ + 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 SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile16BitBE(file, scores->entry[i].score); +} + +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); +} + +static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].time); +} + +static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + int size = strlen(scores->entry[i].tape_basename); + + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0)); + } +} + +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 (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score file '%s'", filename); + + return; + } + + 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; + + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; + + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); + + putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE); + SaveScore_VERS(file, &scores); + + putFileChunkBE(file, "INFO", info_chunk_size); + SaveScore_INFO(file, &scores); + + putFileChunkBE(file, "NAME", name_chunk_size); + SaveScore_NAME(file, &scores); + + if (has_large_score_values) + { + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); + } + else + { + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); + } + + putFileChunkBE(file, "TIME", time_chunk_size); + SaveScore_TIME(file, &scores); + + putFileChunkBE(file, "TAPE", tape_chunk_size); + SaveScore_TAPE(file, &scores); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} + +void SaveScore(int nr) +{ + 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 LoadServerScoreFromCache(int nr) +{ + struct ScoreEntry score_entry; + struct + { + 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 }, + + { NULL, FALSE, 0 } + }; + char *filename = getScoreCacheFilename(nr); + SetupFileHash *score_hash = loadSetupFileHash(filename); + int i, j; + + server_scores.num_entries = 0; + + if (score_hash == NULL) + return; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + score_entry = server_scores.entry[i]; + + for (j = 0; score_mapping[j].value != NULL; j++) + { + char token[10]; + + sprintf(token, "%02d.%d", i, j); + + char *value = getHashEntry(score_hash, token); + + if (value == NULL) + continue; + + if (score_mapping[j].is_string) + { + char *score_value = (char *)score_mapping[j].value; + int value_size = score_mapping[j].string_size; + + strncpy(score_value, value, value_size); + score_value[value_size] = '\0'; + } + else + { + int *score_value = (int *)score_mapping[j].value; + + *score_value = atoi(value); + } + + server_scores.num_entries = i + 1; + } + + 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); + } +} + +void PrepareScoreTapesForUpload(char *leveldir_subdir) +{ + 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; + + SaveSetup_ServerSetup(); +} + +void SaveServerScore(int nr, boolean tape_saved) +{ + if (!runtime.use_api_server) + { + PrepareScoreTapesForUpload(leveldir_current->subdir); + + return; + } + + ApiAddScoreAsThread(nr, tape_saved, NULL); +} + +void SaveServerScoreFromFile(int nr, boolean tape_saved, + char *score_tape_filename) +{ + if (!runtime.use_api_server) + return; + + ApiAddScoreAsThread(nr, tape_saved, score_tape_filename); +} + +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(); + + if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY)) + LoadScore(nr); + + // restore last added local score entry (before merging server scores) + scores.last_added = scores.last_added_local = last_added_local; + + if (setup.use_api_server && + !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY)) + { + // load server scores from cache file and trigger update from server + LoadServerScore(nr, download_score); + + // merge local scores with scores from server + MergeServerScore(); + } + + if (force_last_added) + scores.force_last_added = force_last_added; +} + + +// ============================================================================ +// setup file functions +// ============================================================================ + +#define TOKEN_STR_PLAYER_PREFIX "player_" + + +static struct TokenInfo global_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_name, "player_name" + }, + { + TYPE_SWITCH, + &setup.multiple_users, "multiple_users" + }, + { + TYPE_SWITCH, + &setup.sound, "sound" + }, + { + TYPE_SWITCH, + &setup.sound_loops, "repeating_sound_loops" + }, + { + TYPE_SWITCH, + &setup.sound_music, "background_music" + }, + { + TYPE_SWITCH, + &setup.sound_simple, "simple_sound_effects" + }, + { + TYPE_SWITCH, + &setup.toons, "toons" + }, + { + TYPE_SWITCH, + &setup.global_animations, "global_animations" + }, + { + TYPE_SWITCH, + &setup.scroll_delay, "scroll_delay" + }, + { + TYPE_SWITCH, + &setup.forced_scroll_delay, "forced_scroll_delay" + }, + { + TYPE_INTEGER, + &setup.scroll_delay_value, "scroll_delay_value" + }, + { + TYPE_STRING, + &setup.engine_snapshot_mode, "engine_snapshot_mode" + }, + { + TYPE_INTEGER, + &setup.engine_snapshot_memory, "engine_snapshot_memory" + }, + { + TYPE_SWITCH, + &setup.fade_screens, "fade_screens" + }, + { + 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" + }, + { + TYPE_SWITCH, + &setup.quick_doors, "quick_doors" + }, + { + TYPE_SWITCH, + &setup.team_mode, "team_mode" + }, + { + TYPE_SWITCH, + &setup.handicap, "handicap" + }, + { + TYPE_SWITCH, + &setup.skip_levels, "skip_levels" + }, + { + TYPE_SWITCH, + &setup.increment_levels, "increment_levels" + }, + { + TYPE_SWITCH, + &setup.auto_play_next_level, "auto_play_next_level" + }, + { + TYPE_SWITCH, + &setup.count_score_after_game, "count_score_after_game" + }, + { + TYPE_SWITCH, + &setup.show_scores_after_game, "show_scores_after_game" + }, + { + TYPE_SWITCH, + &setup.time_limit, "time_limit" + }, + { + TYPE_SWITCH, + &setup.fullscreen, "fullscreen" + }, + { + TYPE_INTEGER, + &setup.window_scaling_percent, "window_scaling_percent" + }, + { + TYPE_STRING, + &setup.window_scaling_quality, "window_scaling_quality" + }, + { + TYPE_STRING, + &setup.screen_rendering_mode, "screen_rendering_mode" + }, + { + TYPE_STRING, + &setup.vsync_mode, "vsync_mode" + }, + { + TYPE_SWITCH, + &setup.ask_on_escape, "ask_on_escape" + }, + { + TYPE_SWITCH, + &setup.ask_on_escape_editor, "ask_on_escape_editor" + }, + { + TYPE_SWITCH, + &setup.ask_on_game_over, "ask_on_game_over" + }, + { + TYPE_SWITCH, + &setup.ask_on_quit_game, "ask_on_quit_game" + }, + { + TYPE_SWITCH, + &setup.ask_on_quit_program, "ask_on_quit_program" + }, + { + TYPE_SWITCH, + &setup.quick_switch, "quick_player_switch" + }, + { + TYPE_SWITCH, + &setup.input_on_focus, "input_on_focus" + }, + { + TYPE_SWITCH, + &setup.prefer_aga_graphics, "prefer_aga_graphics" + }, + { + TYPE_SWITCH, + &setup.prefer_lowpass_sounds, "prefer_lowpass_sounds" + }, + { + TYPE_SWITCH, + &setup.prefer_extra_panel_items, "prefer_extra_panel_items" + }, + { + TYPE_SWITCH, + &setup.game_speed_extended, "game_speed_extended" + }, + { + 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_SWITCH3, + &setup.bd_smooth_movements, "bd_smooth_movements" + }, + { + TYPE_SWITCH3, + &setup.bd_pushing_graphics, "bd_pushing_graphics" + }, + { + TYPE_SWITCH3, + &setup.bd_up_down_graphics, "bd_up_down_graphics" + }, + { + TYPE_SWITCH3, + &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" + }, + { + TYPE_SWITCH, + &setup.small_game_graphics, "small_game_graphics" + }, + { + TYPE_SWITCH, + &setup.show_load_save_buttons, "show_load_save_buttons" + }, + { + TYPE_SWITCH, + &setup.show_undo_redo_buttons, "show_undo_redo_buttons" + }, + { + TYPE_STRING, + &setup.scores_in_highscore_list, "scores_in_highscore_list" + }, + { + TYPE_STRING, + &setup.graphics_set, "graphics_set" + }, + { + TYPE_STRING, + &setup.sounds_set, "sounds_set" + }, + { + TYPE_STRING, + &setup.music_set, "music_set" + }, + { + TYPE_SWITCH3, + &setup.override_level_graphics, "override_level_graphics" + }, + { + TYPE_SWITCH3, + &setup.override_level_sounds, "override_level_sounds" + }, + { + TYPE_SWITCH3, + &setup.override_level_music, "override_level_music" + }, + { + TYPE_INTEGER, + &setup.volume_simple, "volume_simple" + }, + { + TYPE_INTEGER, + &setup.volume_loops, "volume_loops" + }, + { + TYPE_INTEGER, + &setup.volume_music, "volume_music" + }, + { + TYPE_SWITCH, + &setup.network_mode, "network_mode" + }, + { + TYPE_PLAYER, + &setup.network_player_nr, "network_player" + }, + { + TYPE_STRING, + &setup.network_server_hostname, "network_server_hostname" + }, + { + TYPE_STRING, + &setup.touch.control_type, "touch.control_type" + }, + { + TYPE_INTEGER, + &setup.touch.move_distance, "touch.move_distance" + }, + { + TYPE_INTEGER, + &setup.touch.drop_distance, "touch.drop_distance" + }, + { + TYPE_INTEGER, + &setup.touch.transparency, "touch.transparency" + }, + { + TYPE_INTEGER, + &setup.touch.draw_outlined, "touch.draw_outlined" + }, + { + TYPE_INTEGER, + &setup.touch.draw_pressed, "touch.draw_pressed" + }, + { + TYPE_INTEGER, + &setup.touch.grid_xsize[0], "touch.virtual_buttons.0.xsize" + }, + { + TYPE_INTEGER, + &setup.touch.grid_ysize[0], "touch.virtual_buttons.0.ysize" + }, + { + TYPE_INTEGER, + &setup.touch.grid_xsize[1], "touch.virtual_buttons.1.xsize" + }, + { + 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[] = +{ + { + TYPE_INTEGER, + &setup.auto_setup.editor_zoom_tilesize, "editor.zoom_tilesize" + }, +}; + +static struct TokenInfo server_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_uuid, "player_uuid" + }, + { + TYPE_INTEGER, + &setup.player_version, "player_version" + }, + { + TYPE_SWITCH, + &setup.use_api_server, TEST_PREFIX "use_api_server" + }, + { + TYPE_STRING, + &setup.api_server_hostname, TEST_PREFIX "api_server_hostname" + }, + { + TYPE_STRING, + &setup.api_server_password, TEST_PREFIX "api_server_password" + }, + { + TYPE_SWITCH, + &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_remaining_tapes, TEST_PREFIX "ask_for_remaining_tapes" + }, + { + TYPE_SWITCH, + &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_using_api_server,TEST_PREFIX "ask_for_using_api_server" + }, + { + TYPE_SWITCH, + &setup.has_remaining_tapes, TEST_PREFIX "has_remaining_tapes" + }, +}; + +static struct TokenInfo editor_setup_tokens[] = +{ + { + TYPE_SWITCH, + &setup.editor.el_classic, "editor.el_classic" + }, + { + TYPE_SWITCH, + &setup.editor.el_custom, "editor.el_custom" + }, + { + TYPE_SWITCH, + &setup.editor.el_user_defined, "editor.el_user_defined" + }, + { + TYPE_SWITCH, + &setup.editor.el_dynamic, "editor.el_dynamic" + }, + { + TYPE_SWITCH, + &setup.editor.el_headlines, "editor.el_headlines" + }, + { + TYPE_SWITCH, + &setup.editor.show_element_token, "editor.show_element_token" + }, + { + TYPE_SWITCH, + &setup.editor.show_read_only_warning, "editor.show_read_only_warning" + }, +}; + +static struct TokenInfo editor_cascade_setup_tokens[] = +{ + { + TYPE_SWITCH, + &setup.editor_cascade.el_bd, "editor.cascade.el_bd" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bd_native, "editor.cascade.el_bd_native" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bd_effects, "editor.cascade.el_bd_effects" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_em, "editor.cascade.el_em" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_emc, "editor.cascade.el_emc" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_rnd, "editor.cascade.el_rnd" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_sb, "editor.cascade.el_sb" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_sp, "editor.cascade.el_sp" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_dc, "editor.cascade.el_dc" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_dx, "editor.cascade.el_dx" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_mm, "editor.cascade.el_mm" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_df, "editor.cascade.el_df" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_chars, "editor.cascade.el_chars" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_steel_chars, "editor.cascade.el_steel_chars" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_ce, "editor.cascade.el_ce" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_ge, "editor.cascade.el_ge" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_es, "editor.cascade.el_es" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_ref, "editor.cascade.el_ref" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_user, "editor.cascade.el_user" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_dynamic, "editor.cascade.el_dynamic" + }, +}; + +static struct TokenInfo shortcut_setup_tokens[] = +{ + { + TYPE_KEY_X11, + &setup.shortcut.save_game, "shortcut.save_game" + }, + { + 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" + }, + { + TYPE_KEY_X11, + &setup.shortcut.focus_player[0], "shortcut.focus_player_1" + }, + { + TYPE_KEY_X11, + &setup.shortcut.focus_player[1], "shortcut.focus_player_2" + }, + { + TYPE_KEY_X11, + &setup.shortcut.focus_player[2], "shortcut.focus_player_3" + }, + { + TYPE_KEY_X11, + &setup.shortcut.focus_player[3], "shortcut.focus_player_4" + }, + { + TYPE_KEY_X11, + &setup.shortcut.focus_player_all, "shortcut.focus_player_all" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_eject, "shortcut.tape_eject" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_extra, "shortcut.tape_extra" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_stop, "shortcut.tape_stop" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_pause, "shortcut.tape_pause" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_record, "shortcut.tape_record" + }, + { + TYPE_KEY_X11, + &setup.shortcut.tape_play, "shortcut.tape_play" + }, + { + TYPE_KEY_X11, + &setup.shortcut.sound_simple, "shortcut.sound_simple" + }, + { + TYPE_KEY_X11, + &setup.shortcut.sound_loops, "shortcut.sound_loops" + }, + { + TYPE_KEY_X11, + &setup.shortcut.sound_music, "shortcut.sound_music" + }, + { + TYPE_KEY_X11, + &setup.shortcut.snap_left, "shortcut.snap_left" + }, + { + TYPE_KEY_X11, + &setup.shortcut.snap_right, "shortcut.snap_right" + }, + { + TYPE_KEY_X11, + &setup.shortcut.snap_up, "shortcut.snap_up" + }, + { + TYPE_KEY_X11, + &setup.shortcut.snap_down, "shortcut.snap_down" + }, +}; + +static struct SetupInputInfo setup_input; +static struct TokenInfo player_setup_tokens[] = +{ + { + TYPE_BOOLEAN, + &setup_input.use_joystick, ".use_joystick" + }, + { + TYPE_STRING, + &setup_input.joy.device_name, ".joy.device_name" + }, + { + TYPE_INTEGER, + &setup_input.joy.xleft, ".joy.xleft" + }, + { + TYPE_INTEGER, + &setup_input.joy.xmiddle, ".joy.xmiddle" + }, + { + TYPE_INTEGER, + &setup_input.joy.xright, ".joy.xright" + }, + { + TYPE_INTEGER, + &setup_input.joy.yupper, ".joy.yupper" + }, + { + TYPE_INTEGER, + &setup_input.joy.ymiddle, ".joy.ymiddle" + }, + { + TYPE_INTEGER, + &setup_input.joy.ylower, ".joy.ylower" + }, + { + TYPE_INTEGER, + &setup_input.joy.snap, ".joy.snap_field" + }, + { + TYPE_INTEGER, + &setup_input.joy.drop, ".joy.place_bomb" + }, + { + TYPE_KEY_X11, + &setup_input.key.left, ".key.move_left" + }, + { + TYPE_KEY_X11, + &setup_input.key.right, ".key.move_right" + }, + { + TYPE_KEY_X11, + &setup_input.key.up, ".key.move_up" + }, + { + TYPE_KEY_X11, + &setup_input.key.down, ".key.move_down" + }, + { + TYPE_KEY_X11, + &setup_input.key.snap, ".key.snap_field" + }, + { + TYPE_KEY_X11, + &setup_input.key.drop, ".key.place_bomb" + }, +}; + +static struct TokenInfo system_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.system.sdl_renderdriver, "system.sdl_renderdriver" + }, + { + TYPE_STRING, + &setup.system.sdl_videodriver, "system.sdl_videodriver" + }, + { + TYPE_STRING, + &setup.system.sdl_audiodriver, "system.sdl_audiodriver" + }, + { + TYPE_INTEGER, + &setup.system.audio_fragment_size, "system.audio_fragment_size" + }, +}; + +static struct TokenInfo internal_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.internal.program_title, "program_title" + }, + { + TYPE_STRING, + &setup.internal.program_version, "program_version" + }, + { + TYPE_STRING, + &setup.internal.program_author, "program_author" + }, + { + TYPE_STRING, + &setup.internal.program_email, "program_email" + }, + { + TYPE_STRING, + &setup.internal.program_website, "program_website" + }, + { + TYPE_STRING, + &setup.internal.program_copyright, "program_copyright" + }, + { + TYPE_STRING, + &setup.internal.program_company, "program_company" + }, + { + TYPE_STRING, + &setup.internal.program_icon_file, "program_icon_file" + }, + { + TYPE_STRING, + &setup.internal.default_graphics_set, "default_graphics_set" + }, + { + TYPE_STRING, + &setup.internal.default_sounds_set, "default_sounds_set" + }, + { + TYPE_STRING, + &setup.internal.default_music_set, "default_music_set" + }, + { + TYPE_STRING, + &setup.internal.fallback_graphics_file, "fallback_graphics_file" + }, + { + TYPE_STRING, + &setup.internal.fallback_sounds_file, "fallback_sounds_file" + }, + { + TYPE_STRING, + &setup.internal.fallback_music_file, "fallback_music_file" + }, + { + TYPE_STRING, + &setup.internal.default_level_series, "default_level_series" + }, + { + TYPE_INTEGER, + &setup.internal.default_window_width, "default_window_width" + }, + { + TYPE_INTEGER, + &setup.internal.default_window_height, "default_window_height" + }, + { + TYPE_BOOLEAN, + &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.menu_shortcuts_tape, "menu_shortcuts_tape" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_title, "info_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_elements, "info_elements" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_music, "info_music" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_credits, "info_credits" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_program, "info_program" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_version, "info_version" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_levelset, "info_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_exit, "info_exit" + }, +}; + +static struct TokenInfo debug_setup_tokens[] = +{ + { + TYPE_INTEGER, + &setup.debug.frame_delay[0], "debug.frame_delay_0" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[1], "debug.frame_delay_1" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[2], "debug.frame_delay_2" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[3], "debug.frame_delay_3" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[4], "debug.frame_delay_4" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[5], "debug.frame_delay_5" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[6], "debug.frame_delay_6" + }, + { + TYPE_INTEGER, + &setup.debug.frame_delay[7], "debug.frame_delay_7" + }, { - tape.no_valid_file = TRUE; - - return; - } - - getFileChunkBE(file, chunk_name, NULL); - if (strEqual(chunk_name, "RND1")) + TYPE_INTEGER, + &setup.debug.frame_delay[8], "debug.frame_delay_8" + }, { - getFile32BitBE(file); // not used - - getFileChunkBE(file, chunk_name, NULL); - if (!strEqual(chunk_name, "TAPE")) - { - tape.no_valid_file = TRUE; - - Error(ERR_WARN, "unknown format of tape file '%s'", filename); - - closeFile(file); - - return; - } - } - else // check for pre-2.0 file format with cookie string + TYPE_INTEGER, + &setup.debug.frame_delay[9], "debug.frame_delay_9" + }, { - 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'; - - if (!checkCookieString(cookie, TAPE_COOKIE_TMPL)) - { - tape.no_valid_file = TRUE; - - Error(ERR_WARN, "unknown format of tape file '%s'", filename); - - closeFile(file); - - return; - } - - if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1) - { - tape.no_valid_file = TRUE; - - Error(ERR_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 (tape.file_version < FILE_VERSION_1_2) + TYPE_KEY_X11, + &setup.debug.frame_delay_key[0], "debug.key.frame_delay_0" + }, { - // 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 + TYPE_KEY_X11, + &setup.debug.frame_delay_key[1], "debug.key.frame_delay_1" + }, { - 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 }, - { "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) - { - Error(ERR_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) - { - Error(ERR_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) - { - Error(ERR_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 - printf("::: tape file version: %d\n", tape.file_version); - printf("::: tape game version: %d\n", tape.game_version); - printf("::: tape engine version: %d\n", 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) && - level.game_engine_type == GAME_ENGINE_TYPE_SP && - level.native_sp_level->demo.is_available) - CopyNativeTape_SP_to_RND(&level); -} + TYPE_KEY_X11, + &setup.debug.frame_delay_key[2], "debug.key.frame_delay_2" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[3], "debug.key.frame_delay_3" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[4], "debug.key.frame_delay_4" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[5], "debug.key.frame_delay_5" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[6], "debug.key.frame_delay_6" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[7], "debug.key.frame_delay_7" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[8], "debug.key.frame_delay_8" + }, + { + TYPE_KEY_X11, + &setup.debug.frame_delay_key[9], "debug.key.frame_delay_9" + }, + { + TYPE_BOOLEAN, + &setup.debug.frame_delay_use_mod_key, "debug.frame_delay.use_mod_key"}, + { + TYPE_BOOLEAN, + &setup.debug.frame_delay_game_only, "debug.frame_delay.game_only" + }, + { + TYPE_BOOLEAN, + &setup.debug.show_frames_per_second, "debug.show_frames_per_second" + }, + { + TYPE_SWITCH3, + &setup.debug.xsn_mode, "debug.xsn_mode" + }, + { + TYPE_INTEGER, + &setup.debug.xsn_percent, "debug.xsn_percent" + }, +}; -static void SaveTape_VERS(FILE *file, struct TapeInfo *tape) +static struct TokenInfo options_setup_tokens[] = { - putFileVersion(file, tape->file_version); - putFileVersion(file, tape->game_version); -} + { + 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 SaveTape_HEAD(FILE *file, struct TapeInfo *tape) +static void setSetupInfoToDefaults(struct SetupInfo *si) { 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, (tape->use_mouse ? 1 : 0)); + si->player_name = getStringCopy(getDefaultUserName(user.nr)); - // unused bytes not at the end here for 4-byte alignment of engine_version - WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED); + si->multiple_users = TRUE; - putFileVersion(file, tape->engine_version); -} + si->sound = TRUE; + si->sound_loops = TRUE; + 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; + si->engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_DEFAULT); + 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->increment_levels = TRUE; + si->auto_play_next_level = TRUE; + si->count_score_after_game = TRUE; + si->show_scores_after_game = TRUE; + si->time_limit = TRUE; + si->fullscreen = FALSE; + si->window_scaling_percent = STD_WINDOW_SCALING_PERCENT; + si->window_scaling_quality = getStringCopy(SCALING_QUALITY_DEFAULT); + si->screen_rendering_mode = getStringCopy(STR_SPECIAL_RENDERING_DEFAULT); + si->vsync_mode = getStringCopy(STR_VSYNC_MODE_DEFAULT); + si->ask_on_escape = TRUE; + si->ask_on_escape_editor = TRUE; + si->ask_on_game_over = TRUE; + si->ask_on_quit_game = TRUE; + si->ask_on_quit_program = TRUE; + si->quick_switch = FALSE; + si->input_on_focus = FALSE; + si->prefer_aga_graphics = TRUE; + si->prefer_lowpass_sounds = FALSE; + 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 = AUTO; + si->bd_pushing_graphics = TRUE; + si->bd_up_down_graphics = TRUE; + si->bd_skip_falling_sounds = 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; + si->show_undo_redo_buttons = FALSE; + si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT); -static void SaveTape_INFO(FILE *file, struct TapeInfo *tape) -{ - int level_identifier_size = strlen(tape->level_identifier) + 1; - int i; + si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); + si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); + si->music_set = getStringCopy(MUS_CLASSIC_SUBDIR); - putFile16BitBE(file, level_identifier_size); + si->override_level_graphics = FALSE; + si->override_level_sounds = FALSE; + si->override_level_music = FALSE; - for (i = 0; i < level_identifier_size; i++) - putFile8Bit(file, tape->level_identifier[i]); + si->volume_simple = 100; // percent + si->volume_loops = 100; // percent + si->volume_music = 100; // percent - putFile16BitBE(file, tape->level_nr); -} + si->network_mode = FALSE; + si->network_player_nr = 0; // first player + si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT); -static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) -{ - int i, j; + si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT); + si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT; // percent + si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT; // percent + si->touch.transparency = TOUCH_TRANSPARENCY_DEFAULT; // percent + si->touch.draw_outlined = TRUE; + si->touch.draw_pressed = TRUE; - for (i = 0; i < tape->length; i++) + for (i = 0; i < 2; i++) { - if (tape->use_mouse) + char *default_grid_button[6][2] = { - 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]); + { " ", " ^^ " }, + { " ", " ^^ " }, + { " ", "<< >>" }, + { " ", "<< >>" }, + { "111222", " vv " }, + { "111222", " vv " } + }; + int grid_xsize = DEFAULT_GRID_XSIZE(i); + int grid_ysize = DEFAULT_GRID_YSIZE(i); + int min_xsize = MIN(6, grid_xsize); + int min_ysize = MIN(6, grid_ysize); + int startx = grid_xsize - min_xsize; + int starty = grid_ysize - min_ysize; + int x, y; + + // virtual buttons grid can only be set to defaults if video is initialized + // (this will be repeated if virtual buttons are not loaded from setup file) + if (video.initialized) + { + si->touch.grid_xsize[i] = grid_xsize; + si->touch.grid_ysize[i] = grid_ysize; } else { - for (j = 0; j < MAX_PLAYERS; j++) - if (tape->player_participates[j]) - putFile8Bit(file, tape->pos[i].action[j]); + si->touch.grid_xsize[i] = -1; + si->touch.grid_ysize[i] = -1; } - putFile8Bit(file, tape->pos[i].delay); - } -} - -void SaveTape(int nr) -{ - char *filename = getTapeFilename(nr); - FILE *file; - int num_participating_players = 0; - int tape_pos_size; - int info_chunk_size; - int body_chunk_size; - int i; + for (x = 0; x < MAX_GRID_XSIZE; x++) + for (y = 0; y < MAX_GRID_YSIZE; y++) + si->touch.grid_button[i][x][y] = CHAR_GRID_BUTTON_NONE; - InitTapeDirectory(leveldir_current->subdir); + for (x = 0; x < min_xsize; x++) + for (y = 0; y < min_ysize; y++) + si->touch.grid_button[i][x][starty + y] = + default_grid_button[y][0][x]; - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot save level recording file '%s'", filename); - return; + for (x = 0; x < min_xsize; x++) + for (y = 0; y < min_ysize; y++) + si->touch.grid_button[i][startx + x][starty + y] = + default_grid_button[y][1][x]; } - tape.file_version = FILE_VERSION_ACTUAL; - tape.game_version = GAME_VERSION_ACTUAL; - - // count number of participating players - for (i = 0; i < MAX_PLAYERS; i++) - if (tape.player_participates[i]) - num_participating_players++; - - tape_pos_size = (tape.use_mouse ? 3 : num_participating_players) + 1; - - 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); + si->touch.grid_initialized = video.initialized; - putFileChunkBE(file, "VERS", TAPE_CHUNK_VERS_SIZE); - SaveTape_VERS(file, &tape); + si->touch.overlay_buttons = FALSE; - putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE); - SaveTape_HEAD(file, &tape); + 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; + si->editor.el_sokoban = TRUE; + si->editor.el_supaplex = TRUE; + si->editor.el_diamond_caves = TRUE; + si->editor.el_dx_boulderdash = TRUE; - putFileChunkBE(file, "INFO", info_chunk_size); - SaveTape_INFO(file, &tape); + si->editor.el_mirror_magic = TRUE; + si->editor.el_deflektor = TRUE; - putFileChunkBE(file, "BODY", body_chunk_size); - SaveTape_BODY(file, &tape); + si->editor.el_chars = TRUE; + si->editor.el_steel_chars = TRUE; - fclose(file); + si->editor.el_classic = TRUE; + si->editor.el_custom = TRUE; - SetFilePermissions(filename, PERMS_PRIVATE); + si->editor.el_user_defined = FALSE; + si->editor.el_dynamic = TRUE; - tape.changed = FALSE; -} + si->editor.el_headlines = TRUE; -static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved) -{ - char *filename = getTapeFilename(nr); - boolean new_tape = !fileExists(filename); - boolean tape_saved = FALSE; + si->editor.show_element_token = FALSE; - if (new_tape || Request(msg_replace, REQ_ASK)) - { - SaveTape(nr); + si->editor.show_read_only_warning = TRUE; - if (new_tape) - Request(msg_saved, REQ_CONFIRM); + si->editor.use_template_for_new_levels = TRUE; - tape_saved = TRUE; - } + 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; - return tape_saved; -} + si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; + si->shortcut.focus_player[1] = DEFAULT_KEY_FOCUS_PLAYER_2; + si->shortcut.focus_player[2] = DEFAULT_KEY_FOCUS_PLAYER_3; + si->shortcut.focus_player[3] = DEFAULT_KEY_FOCUS_PLAYER_4; + si->shortcut.focus_player_all = DEFAULT_KEY_FOCUS_PLAYER_ALL; -boolean SaveTapeChecked(int nr) -{ - return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!"); -} + si->shortcut.tape_eject = DEFAULT_KEY_TAPE_EJECT; + si->shortcut.tape_extra = DEFAULT_KEY_TAPE_EXTRA; + si->shortcut.tape_stop = DEFAULT_KEY_TAPE_STOP; + si->shortcut.tape_pause = DEFAULT_KEY_TAPE_PAUSE; + si->shortcut.tape_record = DEFAULT_KEY_TAPE_RECORD; + si->shortcut.tape_play = DEFAULT_KEY_TAPE_PLAY; -boolean SaveTapeChecked_LevelSolved(int nr) -{ - return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?", - "Level solved! Tape saved!"); -} + si->shortcut.sound_simple = DEFAULT_KEY_SOUND_SIMPLE; + si->shortcut.sound_loops = DEFAULT_KEY_SOUND_LOOPS; + si->shortcut.sound_music = DEFAULT_KEY_SOUND_MUSIC; -void DumpTape(struct TapeInfo *tape) -{ - int tape_frame_counter; - int i, j; + si->shortcut.snap_left = DEFAULT_KEY_SNAP_LEFT; + si->shortcut.snap_right = DEFAULT_KEY_SNAP_RIGHT; + si->shortcut.snap_up = DEFAULT_KEY_SNAP_UP; + si->shortcut.snap_down = DEFAULT_KEY_SNAP_DOWN; - if (tape->no_valid_file) + for (i = 0; i < MAX_PLAYERS; i++) { - Error(ERR_WARN, "cannot dump -- no valid tape file found"); - - return; + si->input[i].use_joystick = FALSE; + 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; + si->input[i].joy.yupper = JOYSTICK_YUPPER; + si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE; + si->input[i].joy.ylower = JOYSTICK_YLOWER; + si->input[i].joy.snap = (i == 0 ? JOY_BUTTON_1 : 0); + si->input[i].joy.drop = (i == 0 ? JOY_BUTTON_2 : 0); + si->input[i].key.left = (i == 0 ? DEFAULT_KEY_LEFT : KSYM_UNDEFINED); + si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED); + si->input[i].key.up = (i == 0 ? DEFAULT_KEY_UP : KSYM_UNDEFINED); + si->input[i].key.down = (i == 0 ? DEFAULT_KEY_DOWN : KSYM_UNDEFINED); + si->input[i].key.snap = (i == 0 ? DEFAULT_KEY_SNAP : KSYM_UNDEFINED); + si->input[i].key.drop = (i == 0 ? DEFAULT_KEY_DROP : KSYM_UNDEFINED); } - 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); - PrintLine("-", 79); - - tape_frame_counter = 0; - - for (i = 0; i < tape->length; i++) - { - if (i >= MAX_TAPE_LEN) - break; - - 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' : ' ')); - } - } + si->system.sdl_renderdriver = getStringCopy(ARG_DEFAULT); + si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT); + si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT); + si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE; - Print("(%03d) ", tape->pos[i].delay); - Print("[%05d]\n", tape_frame_counter); + si->internal.program_title = getStringCopy(PROGRAM_TITLE_STRING); + si->internal.program_version = getStringCopy(getProgramRealVersionString()); + si->internal.program_author = getStringCopy(PROGRAM_AUTHOR_STRING); + si->internal.program_email = getStringCopy(PROGRAM_EMAIL_STRING); + si->internal.program_website = getStringCopy(PROGRAM_WEBSITE_STRING); + si->internal.program_copyright = getStringCopy(PROGRAM_COPYRIGHT_STRING); + si->internal.program_company = getStringCopy(PROGRAM_COMPANY_STRING); - tape_frame_counter += tape->pos[i].delay; - } + si->internal.program_icon_file = getStringCopy(PROGRAM_ICON_FILENAME); - PrintLine("-", 79); -} + si->internal.default_graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); + si->internal.default_sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); + si->internal.default_music_set = getStringCopy(MUS_CLASSIC_SUBDIR); + si->internal.fallback_graphics_file = getStringCopy(UNDEFINED_FILENAME); + si->internal.fallback_sounds_file = getStringCopy(UNDEFINED_FILENAME); + si->internal.fallback_music_file = getStringCopy(UNDEFINED_FILENAME); -// ============================================================================ -// score file functions -// ============================================================================ + si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET); + 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; -void LoadScore(int nr) -{ - int i; - char *filename = getScoreFilename(nr); - char cookie[MAX_LINE_LEN]; - char line[MAX_LINE_LEN]; - char *line_ptr; - FILE *file; + si->internal.default_window_width = WIN_XSIZE_DEFAULT; + si->internal.default_window_height = WIN_YSIZE_DEFAULT; - // always start with reliable default values - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - { - strcpy(highscore[i].Name, EMPTY_PLAYER_NAME); - highscore[i].Score = 0; - } + si->debug.frame_delay[0] = DEFAULT_FRAME_DELAY_0; + si->debug.frame_delay[1] = DEFAULT_FRAME_DELAY_1; + si->debug.frame_delay[2] = DEFAULT_FRAME_DELAY_2; + si->debug.frame_delay[3] = DEFAULT_FRAME_DELAY_3; + si->debug.frame_delay[4] = DEFAULT_FRAME_DELAY_4; + si->debug.frame_delay[5] = DEFAULT_FRAME_DELAY_5; + si->debug.frame_delay[6] = DEFAULT_FRAME_DELAY_6; + si->debug.frame_delay[7] = DEFAULT_FRAME_DELAY_7; + si->debug.frame_delay[8] = DEFAULT_FRAME_DELAY_8; + si->debug.frame_delay[9] = DEFAULT_FRAME_DELAY_9; - if (!(file = fopen(filename, MODE_READ))) - return; + si->debug.frame_delay_key[0] = DEFAULT_KEY_FRAME_DELAY_0; + si->debug.frame_delay_key[1] = DEFAULT_KEY_FRAME_DELAY_1; + si->debug.frame_delay_key[2] = DEFAULT_KEY_FRAME_DELAY_2; + si->debug.frame_delay_key[3] = DEFAULT_KEY_FRAME_DELAY_3; + si->debug.frame_delay_key[4] = DEFAULT_KEY_FRAME_DELAY_4; + si->debug.frame_delay_key[5] = DEFAULT_KEY_FRAME_DELAY_5; + si->debug.frame_delay_key[6] = DEFAULT_KEY_FRAME_DELAY_6; + si->debug.frame_delay_key[7] = DEFAULT_KEY_FRAME_DELAY_7; + si->debug.frame_delay_key[8] = DEFAULT_KEY_FRAME_DELAY_8; + si->debug.frame_delay_key[9] = DEFAULT_KEY_FRAME_DELAY_9; - // 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'; + si->debug.frame_delay_use_mod_key = DEFAULT_FRAME_DELAY_USE_MOD_KEY; + si->debug.frame_delay_game_only = DEFAULT_FRAME_DELAY_GAME_ONLY; - if (!checkCookieString(cookie, SCORE_COOKIE)) - { - Error(ERR_WARN, "unknown format of score file '%s'", filename); - fclose(file); - return; - } + si->debug.show_frames_per_second = FALSE; - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - { - if (fscanf(file, "%d", &highscore[i].Score) == EOF) - Error(ERR_WARN, "fscanf() failed; %s", strerror(errno)); - if (fgets(line, MAX_LINE_LEN, file) == NULL) - line[0] = '\0'; + si->debug.xsn_mode = AUTO; + si->debug.xsn_percent = 0; - if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') - line[strlen(line) - 1] = '\0'; + si->options.verbose = FALSE; + si->options.debug = FALSE; + si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING); - for (line_ptr = line; *line_ptr; line_ptr++) - { - if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') - { - strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN); - highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0'; - break; - } - } - } +#if defined(PLATFORM_ANDROID) + si->fullscreen = TRUE; + si->touch.overlay_buttons = TRUE; +#endif - fclose(file); + setHideSetupEntry(&setup.debug.xsn_mode); } -void SaveScore(int nr) +static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) { - int i; - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); - char *filename = getScoreFilename(nr); - FILE *file; - - // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE; +} - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot save score for level %d", nr); - return; - } +static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si) +{ + si->player_uuid = NULL; // (will be set later) + si->player_version = 1; // (will be set later) - fprintf(file, "%s\n\n", SCORE_COOKIE); + si->use_api_server = TRUE; + si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME); + si->api_server_password = getStringCopy(UNDEFINED_PASSWORD); + si->ask_for_uploading_tapes = TRUE; + si->ask_for_remaining_tapes = FALSE; + si->provide_uploading_tapes = TRUE; + si->ask_for_using_api_server = TRUE; + si->has_remaining_tapes = FALSE; +} - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name); +static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) +{ + si->editor_cascade.el_bd = TRUE; + si->editor_cascade.el_bd_native = TRUE; + si->editor_cascade.el_bd_effects = FALSE; + si->editor_cascade.el_em = TRUE; + si->editor_cascade.el_emc = TRUE; + si->editor_cascade.el_rnd = TRUE; + si->editor_cascade.el_sb = TRUE; + si->editor_cascade.el_sp = TRUE; + si->editor_cascade.el_dc = TRUE; + si->editor_cascade.el_dx = TRUE; - fclose(file); + si->editor_cascade.el_mm = TRUE; + si->editor_cascade.el_df = TRUE; - SetFilePermissions(filename, permissions); + si->editor_cascade.el_chars = FALSE; + si->editor_cascade.el_steel_chars = FALSE; + si->editor_cascade.el_ce = FALSE; + si->editor_cascade.el_ge = FALSE; + si->editor_cascade.el_es = FALSE; + si->editor_cascade.el_ref = FALSE; + si->editor_cascade.el_user = FALSE; + si->editor_cascade.el_dynamic = FALSE; } +#define MAX_HIDE_SETUP_TOKEN_SIZE 20 -// ============================================================================ -// setup file functions -// ============================================================================ +static char *getHideSetupToken(void *setup_value) +{ + static char hide_setup_token[MAX_HIDE_SETUP_TOKEN_SIZE]; -#define TOKEN_STR_PLAYER_PREFIX "player_" + if (setup_value != NULL) + snprintf(hide_setup_token, MAX_HIDE_SETUP_TOKEN_SIZE, "%p", setup_value); -// global setup -enum -{ - SETUP_TOKEN_PLAYER_NAME = 0, - SETUP_TOKEN_SOUND, - SETUP_TOKEN_SOUND_LOOPS, - SETUP_TOKEN_SOUND_MUSIC, - SETUP_TOKEN_SOUND_SIMPLE, - SETUP_TOKEN_TOONS, - SETUP_TOKEN_SCROLL_DELAY, - SETUP_TOKEN_SCROLL_DELAY_VALUE, - SETUP_TOKEN_ENGINE_SNAPSHOT_MODE, - SETUP_TOKEN_ENGINE_SNAPSHOT_MEMORY, - SETUP_TOKEN_FADE_SCREENS, - SETUP_TOKEN_AUTORECORD, - SETUP_TOKEN_SHOW_TITLESCREEN, - SETUP_TOKEN_QUICK_DOORS, - SETUP_TOKEN_TEAM_MODE, - SETUP_TOKEN_HANDICAP, - SETUP_TOKEN_SKIP_LEVELS, - SETUP_TOKEN_INCREMENT_LEVELS, - SETUP_TOKEN_AUTO_PLAY_NEXT_LEVEL, - SETUP_TOKEN_SKIP_SCORES_AFTER_GAME, - SETUP_TOKEN_TIME_LIMIT, - SETUP_TOKEN_FULLSCREEN, - SETUP_TOKEN_WINDOW_SCALING_PERCENT, - SETUP_TOKEN_WINDOW_SCALING_QUALITY, - SETUP_TOKEN_SCREEN_RENDERING_MODE, - SETUP_TOKEN_VSYNC_MODE, - SETUP_TOKEN_ASK_ON_ESCAPE, - SETUP_TOKEN_ASK_ON_ESCAPE_EDITOR, - SETUP_TOKEN_ASK_ON_GAME_OVER, - SETUP_TOKEN_QUICK_SWITCH, - SETUP_TOKEN_INPUT_ON_FOCUS, - SETUP_TOKEN_PREFER_AGA_GRAPHICS, - SETUP_TOKEN_GAME_SPEED_EXTENDED, - SETUP_TOKEN_GAME_FRAME_DELAY, - SETUP_TOKEN_SP_SHOW_BORDER_ELEMENTS, - SETUP_TOKEN_SMALL_GAME_GRAPHICS, - SETUP_TOKEN_SHOW_SNAPSHOT_BUTTONS, - SETUP_TOKEN_GRAPHICS_SET, - SETUP_TOKEN_SOUNDS_SET, - SETUP_TOKEN_MUSIC_SET, - SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS, - SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS, - SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC, - SETUP_TOKEN_VOLUME_SIMPLE, - SETUP_TOKEN_VOLUME_LOOPS, - SETUP_TOKEN_VOLUME_MUSIC, - SETUP_TOKEN_NETWORK_MODE, - SETUP_TOKEN_NETWORK_PLAYER_NR, - SETUP_TOKEN_NETWORK_SERVER_HOSTNAME, - SETUP_TOKEN_TOUCH_CONTROL_TYPE, - SETUP_TOKEN_TOUCH_MOVE_DISTANCE, - SETUP_TOKEN_TOUCH_DROP_DISTANCE, - SETUP_TOKEN_TOUCH_TRANSPARENCY, - SETUP_TOKEN_TOUCH_DRAW_OUTLINED, - SETUP_TOKEN_TOUCH_DRAW_PRESSED, - SETUP_TOKEN_TOUCH_GRID_XSIZE_0, - SETUP_TOKEN_TOUCH_GRID_YSIZE_0, - SETUP_TOKEN_TOUCH_GRID_XSIZE_1, - SETUP_TOKEN_TOUCH_GRID_YSIZE_1, - - NUM_GLOBAL_SETUP_TOKENS -}; + return hide_setup_token; +} -// auto setup -enum +void setHideSetupEntry(void *setup_value) { - SETUP_TOKEN_AUTO_EDITOR_ZOOM_TILESIZE = 0, + char *hide_setup_token = getHideSetupToken(setup_value); - NUM_AUTO_SETUP_TOKENS -}; + if (hide_setup_hash == NULL) + hide_setup_hash = newSetupFileHash(); -// editor setup -enum -{ - SETUP_TOKEN_EDITOR_EL_CLASSIC = 0, - SETUP_TOKEN_EDITOR_EL_CUSTOM, - SETUP_TOKEN_EDITOR_EL_USER_DEFINED, - SETUP_TOKEN_EDITOR_EL_DYNAMIC, - SETUP_TOKEN_EDITOR_EL_HEADLINES, - SETUP_TOKEN_EDITOR_SHOW_ELEMENT_TOKEN, + if (setup_value != NULL) + setHashEntry(hide_setup_hash, hide_setup_token, ""); +} - NUM_EDITOR_SETUP_TOKENS -}; +void removeHideSetupEntry(void *setup_value) +{ + char *hide_setup_token = getHideSetupToken(setup_value); -// editor cascade setup -enum -{ - SETUP_TOKEN_EDITOR_CASCADE_BD = 0, - SETUP_TOKEN_EDITOR_CASCADE_EM, - SETUP_TOKEN_EDITOR_CASCADE_EMC, - SETUP_TOKEN_EDITOR_CASCADE_RND, - SETUP_TOKEN_EDITOR_CASCADE_SB, - SETUP_TOKEN_EDITOR_CASCADE_SP, - SETUP_TOKEN_EDITOR_CASCADE_DC, - SETUP_TOKEN_EDITOR_CASCADE_DX, - SETUP_TOKEN_EDITOR_CASCADE_TEXT, - SETUP_TOKEN_EDITOR_CASCADE_STEELTEXT, - SETUP_TOKEN_EDITOR_CASCADE_CE, - SETUP_TOKEN_EDITOR_CASCADE_GE, - SETUP_TOKEN_EDITOR_CASCADE_REF, - SETUP_TOKEN_EDITOR_CASCADE_USER, - SETUP_TOKEN_EDITOR_CASCADE_DYNAMIC, - - NUM_EDITOR_CASCADE_SETUP_TOKENS -}; + if (setup_value != NULL) + removeHashEntry(hide_setup_hash, hide_setup_token); +} -// shortcut setup -enum -{ - SETUP_TOKEN_SHORTCUT_SAVE_GAME = 0, - SETUP_TOKEN_SHORTCUT_LOAD_GAME, - SETUP_TOKEN_SHORTCUT_TOGGLE_PAUSE, - SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_1, - SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_2, - SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_3, - SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_4, - SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_ALL, - SETUP_TOKEN_SHORTCUT_TAPE_EJECT, - SETUP_TOKEN_SHORTCUT_TAPE_EXTRA, - SETUP_TOKEN_SHORTCUT_TAPE_STOP, - SETUP_TOKEN_SHORTCUT_TAPE_PAUSE, - SETUP_TOKEN_SHORTCUT_TAPE_RECORD, - SETUP_TOKEN_SHORTCUT_TAPE_PLAY, - SETUP_TOKEN_SHORTCUT_SOUND_SIMPLE, - SETUP_TOKEN_SHORTCUT_SOUND_LOOPS, - SETUP_TOKEN_SHORTCUT_SOUND_MUSIC, - SETUP_TOKEN_SHORTCUT_SNAP_LEFT, - SETUP_TOKEN_SHORTCUT_SNAP_RIGHT, - SETUP_TOKEN_SHORTCUT_SNAP_UP, - SETUP_TOKEN_SHORTCUT_SNAP_DOWN, - - NUM_SHORTCUT_SETUP_TOKENS -}; +boolean hideSetupEntry(void *setup_value) +{ + char *hide_setup_token = getHideSetupToken(setup_value); -// player setup -enum -{ - SETUP_TOKEN_PLAYER_USE_JOYSTICK = 0, - SETUP_TOKEN_PLAYER_JOY_DEVICE_NAME, - SETUP_TOKEN_PLAYER_JOY_XLEFT, - SETUP_TOKEN_PLAYER_JOY_XMIDDLE, - SETUP_TOKEN_PLAYER_JOY_XRIGHT, - SETUP_TOKEN_PLAYER_JOY_YUPPER, - SETUP_TOKEN_PLAYER_JOY_YMIDDLE, - SETUP_TOKEN_PLAYER_JOY_YLOWER, - SETUP_TOKEN_PLAYER_JOY_SNAP, - SETUP_TOKEN_PLAYER_JOY_DROP, - SETUP_TOKEN_PLAYER_KEY_LEFT, - SETUP_TOKEN_PLAYER_KEY_RIGHT, - SETUP_TOKEN_PLAYER_KEY_UP, - SETUP_TOKEN_PLAYER_KEY_DOWN, - SETUP_TOKEN_PLAYER_KEY_SNAP, - SETUP_TOKEN_PLAYER_KEY_DROP, - - NUM_PLAYER_SETUP_TOKENS -}; + return (setup_value != NULL && + getHashEntry(hide_setup_hash, hide_setup_token) != NULL); +} -// system setup -enum +static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash, + struct TokenInfo *token_info, + int token_nr, char *token_text) { - SETUP_TOKEN_SYSTEM_SDL_VIDEODRIVER = 0, - SETUP_TOKEN_SYSTEM_SDL_AUDIODRIVER, - SETUP_TOKEN_SYSTEM_AUDIO_FRAGMENT_SIZE, + char *token_hide_text = getStringCat2(token_text, ".hide"); + char *token_hide_value = getHashEntry(setup_file_hash, token_hide_text); - NUM_SYSTEM_SETUP_TOKENS -}; + // set the value of this setup option in the setup option structure + setSetupInfo(token_info, token_nr, getHashEntry(setup_file_hash, token_text)); -// internal setup -enum -{ - SETUP_TOKEN_INT_PROGRAM_TITLE = 0, - SETUP_TOKEN_INT_PROGRAM_VERSION, - SETUP_TOKEN_INT_PROGRAM_AUTHOR, - SETUP_TOKEN_INT_PROGRAM_EMAIL, - SETUP_TOKEN_INT_PROGRAM_WEBSITE, - SETUP_TOKEN_INT_PROGRAM_COPYRIGHT, - SETUP_TOKEN_INT_PROGRAM_COMPANY, - SETUP_TOKEN_INT_PROGRAM_ICON_FILE, - SETUP_TOKEN_INT_DEFAULT_GRAPHICS_SET, - SETUP_TOKEN_INT_DEFAULT_SOUNDS_SET, - SETUP_TOKEN_INT_DEFAULT_MUSIC_SET, - SETUP_TOKEN_INT_FALLBACK_GRAPHICS_FILE, - SETUP_TOKEN_INT_FALLBACK_SOUNDS_FILE, - SETUP_TOKEN_INT_FALLBACK_MUSIC_FILE, - SETUP_TOKEN_INT_DEFAULT_LEVEL_SERIES, - SETUP_TOKEN_INT_CHOOSE_FROM_TOP_LEVELDIR, - SETUP_TOKEN_INT_SHOW_SCALING_IN_TITLE, - SETUP_TOKEN_INT_DEFAULT_WINDOW_WIDTH, - SETUP_TOKEN_INT_DEFAULT_WINDOW_HEIGHT, - - NUM_INTERNAL_SETUP_TOKENS -}; + // check if this setup option should be hidden in the setup menu + if (token_hide_value != NULL && get_boolean_from_string(token_hide_value)) + setHideSetupEntry(token_info[token_nr].value); -// debug setup -enum -{ - SETUP_TOKEN_DEBUG_FRAME_DELAY_0 = 0, - SETUP_TOKEN_DEBUG_FRAME_DELAY_1, - SETUP_TOKEN_DEBUG_FRAME_DELAY_2, - SETUP_TOKEN_DEBUG_FRAME_DELAY_3, - SETUP_TOKEN_DEBUG_FRAME_DELAY_4, - SETUP_TOKEN_DEBUG_FRAME_DELAY_5, - SETUP_TOKEN_DEBUG_FRAME_DELAY_6, - SETUP_TOKEN_DEBUG_FRAME_DELAY_7, - SETUP_TOKEN_DEBUG_FRAME_DELAY_8, - SETUP_TOKEN_DEBUG_FRAME_DELAY_9, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_0, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_1, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_2, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_3, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_4, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_5, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_6, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_7, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_8, - SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_9, - SETUP_TOKEN_DEBUG_FRAME_DELAY_USE_MOD_KEY, - SETUP_TOKEN_DEBUG_FRAME_DELAY_GAME_ONLY, - SETUP_TOKEN_DEBUG_SHOW_FRAMES_PER_SECOND, - - NUM_DEBUG_SETUP_TOKENS -}; + free(token_hide_text); +} -// options setup -enum +static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, + struct TokenInfo *token_info, + int token_nr) { - SETUP_TOKEN_OPTIONS_VERBOSE = 0, + setSetupInfoFromTokenText(setup_file_hash, token_info, token_nr, + token_info[token_nr].text); +} - NUM_OPTIONS_SETUP_TOKENS -}; +static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash) +{ + int i, pnr; + if (!setup_file_hash) + return; -static struct SetupInfo si; -static struct SetupAutoSetupInfo sasi; -static struct SetupEditorInfo sei; -static struct SetupEditorCascadeInfo seci; -static struct SetupShortcutInfo ssi; -static struct SetupInputInfo sii; -static struct SetupSystemInfo syi; -static struct SetupInternalInfo sxi; -static struct SetupDebugInfo sdi; -static struct OptionInfo soi; + for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i); -static struct TokenInfo global_setup_tokens[] = -{ - { TYPE_STRING, &si.player_name, "player_name" }, - { TYPE_SWITCH, &si.sound, "sound" }, - { TYPE_SWITCH, &si.sound_loops, "repeating_sound_loops" }, - { TYPE_SWITCH, &si.sound_music, "background_music" }, - { TYPE_SWITCH, &si.sound_simple, "simple_sound_effects" }, - { TYPE_SWITCH, &si.toons, "toons" }, - { TYPE_SWITCH, &si.scroll_delay, "scroll_delay" }, - { TYPE_INTEGER,&si.scroll_delay_value, "scroll_delay_value" }, - { TYPE_STRING, &si.engine_snapshot_mode, "engine_snapshot_mode" }, - { TYPE_INTEGER,&si.engine_snapshot_memory, "engine_snapshot_memory" }, - { TYPE_SWITCH, &si.fade_screens, "fade_screens" }, - { TYPE_SWITCH, &si.autorecord, "automatic_tape_recording"}, - { TYPE_SWITCH, &si.show_titlescreen, "show_titlescreen" }, - { TYPE_SWITCH, &si.quick_doors, "quick_doors" }, - { TYPE_SWITCH, &si.team_mode, "team_mode" }, - { TYPE_SWITCH, &si.handicap, "handicap" }, - { TYPE_SWITCH, &si.skip_levels, "skip_levels" }, - { TYPE_SWITCH, &si.increment_levels, "increment_levels" }, - { TYPE_SWITCH, &si.auto_play_next_level, "auto_play_next_level" }, - { TYPE_SWITCH, &si.skip_scores_after_game, "skip_scores_after_game" }, - { TYPE_SWITCH, &si.time_limit, "time_limit" }, - { TYPE_SWITCH, &si.fullscreen, "fullscreen" }, - { TYPE_INTEGER,&si.window_scaling_percent, "window_scaling_percent" }, - { TYPE_STRING, &si.window_scaling_quality, "window_scaling_quality" }, - { TYPE_STRING, &si.screen_rendering_mode, "screen_rendering_mode" }, - { TYPE_STRING, &si.vsync_mode, "vsync_mode" }, - { TYPE_SWITCH, &si.ask_on_escape, "ask_on_escape" }, - { TYPE_SWITCH, &si.ask_on_escape_editor, "ask_on_escape_editor" }, - { TYPE_SWITCH, &si.ask_on_game_over, "ask_on_game_over" }, - { TYPE_SWITCH, &si.quick_switch, "quick_player_switch" }, - { TYPE_SWITCH, &si.input_on_focus, "input_on_focus" }, - { TYPE_SWITCH, &si.prefer_aga_graphics, "prefer_aga_graphics" }, - { TYPE_SWITCH, &si.game_speed_extended, "game_speed_extended" }, - { TYPE_INTEGER,&si.game_frame_delay, "game_frame_delay" }, - { TYPE_SWITCH, &si.sp_show_border_elements, "sp_show_border_elements" }, - { TYPE_SWITCH, &si.small_game_graphics, "small_game_graphics" }, - { TYPE_SWITCH, &si.show_snapshot_buttons, "show_snapshot_buttons" }, - { TYPE_STRING, &si.graphics_set, "graphics_set" }, - { TYPE_STRING, &si.sounds_set, "sounds_set" }, - { TYPE_STRING, &si.music_set, "music_set" }, - { TYPE_SWITCH3,&si.override_level_graphics, "override_level_graphics" }, - { TYPE_SWITCH3,&si.override_level_sounds, "override_level_sounds" }, - { TYPE_SWITCH3,&si.override_level_music, "override_level_music" }, - { TYPE_INTEGER,&si.volume_simple, "volume_simple" }, - { TYPE_INTEGER,&si.volume_loops, "volume_loops" }, - { TYPE_INTEGER,&si.volume_music, "volume_music" }, - { TYPE_SWITCH, &si.network_mode, "network_mode" }, - { TYPE_PLAYER, &si.network_player_nr, "network_player" }, - { TYPE_STRING, &si.network_server_hostname, "network_server_hostname" }, - { TYPE_STRING, &si.touch.control_type, "touch.control_type" }, - { TYPE_INTEGER,&si.touch.move_distance, "touch.move_distance" }, - { TYPE_INTEGER,&si.touch.drop_distance, "touch.drop_distance" }, - { TYPE_INTEGER,&si.touch.transparency, "touch.transparency" }, - { TYPE_INTEGER,&si.touch.draw_outlined, "touch.draw_outlined" }, - { TYPE_INTEGER,&si.touch.draw_pressed, "touch.draw_pressed" }, - { TYPE_INTEGER,&si.touch.grid_xsize[0], "touch.virtual_buttons.0.xsize" }, - { TYPE_INTEGER,&si.touch.grid_ysize[0], "touch.virtual_buttons.0.ysize" }, - { TYPE_INTEGER,&si.touch.grid_xsize[1], "touch.virtual_buttons.1.xsize" }, - { TYPE_INTEGER,&si.touch.grid_ysize[1], "touch.virtual_buttons.1.ysize" }, -}; + setup.touch.grid_initialized = TRUE; + for (i = 0; i < 2; i++) + { + int grid_xsize = setup.touch.grid_xsize[i]; + int grid_ysize = setup.touch.grid_ysize[i]; + int x, y; -static struct TokenInfo auto_setup_tokens[] = -{ - { TYPE_INTEGER,&sasi.editor_zoom_tilesize, "editor.zoom_tilesize" }, -}; + // if virtual buttons are not loaded from setup file, repeat initializing + // virtual buttons grid with default values later when video is initialized + if (grid_xsize == -1 || + grid_ysize == -1) + { + setup.touch.grid_initialized = FALSE; -static struct TokenInfo editor_setup_tokens[] = -{ - { TYPE_SWITCH, &sei.el_classic, "editor.el_classic" }, - { TYPE_SWITCH, &sei.el_custom, "editor.el_custom" }, - { TYPE_SWITCH, &sei.el_user_defined, "editor.el_user_defined" }, - { TYPE_SWITCH, &sei.el_dynamic, "editor.el_dynamic" }, - { TYPE_SWITCH, &sei.el_headlines, "editor.el_headlines" }, - { TYPE_SWITCH, &sei.show_element_token,"editor.show_element_token" }, -}; + continue; + } -static struct TokenInfo editor_cascade_setup_tokens[] = -{ - { TYPE_SWITCH, &seci.el_bd, "editor.cascade.el_bd" }, - { TYPE_SWITCH, &seci.el_em, "editor.cascade.el_em" }, - { TYPE_SWITCH, &seci.el_emc, "editor.cascade.el_emc" }, - { TYPE_SWITCH, &seci.el_rnd, "editor.cascade.el_rnd" }, - { TYPE_SWITCH, &seci.el_sb, "editor.cascade.el_sb" }, - { TYPE_SWITCH, &seci.el_sp, "editor.cascade.el_sp" }, - { TYPE_SWITCH, &seci.el_dc, "editor.cascade.el_dc" }, - { TYPE_SWITCH, &seci.el_dx, "editor.cascade.el_dx" }, - { TYPE_SWITCH, &seci.el_mm, "editor.cascade.el_mm" }, - { TYPE_SWITCH, &seci.el_df, "editor.cascade.el_df" }, - { TYPE_SWITCH, &seci.el_chars, "editor.cascade.el_chars" }, - { TYPE_SWITCH, &seci.el_steel_chars, "editor.cascade.el_steel_chars" }, - { TYPE_SWITCH, &seci.el_ce, "editor.cascade.el_ce" }, - { TYPE_SWITCH, &seci.el_ge, "editor.cascade.el_ge" }, - { TYPE_SWITCH, &seci.el_ref, "editor.cascade.el_ref" }, - { TYPE_SWITCH, &seci.el_user, "editor.cascade.el_user" }, - { TYPE_SWITCH, &seci.el_dynamic, "editor.cascade.el_dynamic" }, -}; + for (y = 0; y < grid_ysize; y++) + { + char token_string[MAX_LINE_LEN]; -static struct TokenInfo shortcut_setup_tokens[] = -{ - { TYPE_KEY_X11, &ssi.save_game, "shortcut.save_game" }, - { TYPE_KEY_X11, &ssi.load_game, "shortcut.load_game" }, - { TYPE_KEY_X11, &ssi.toggle_pause, "shortcut.toggle_pause" }, - { TYPE_KEY_X11, &ssi.focus_player[0], "shortcut.focus_player_1" }, - { TYPE_KEY_X11, &ssi.focus_player[1], "shortcut.focus_player_2" }, - { TYPE_KEY_X11, &ssi.focus_player[2], "shortcut.focus_player_3" }, - { TYPE_KEY_X11, &ssi.focus_player[3], "shortcut.focus_player_4" }, - { TYPE_KEY_X11, &ssi.focus_player_all,"shortcut.focus_player_all" }, - { TYPE_KEY_X11, &ssi.tape_eject, "shortcut.tape_eject" }, - { TYPE_KEY_X11, &ssi.tape_extra, "shortcut.tape_extra" }, - { TYPE_KEY_X11, &ssi.tape_stop, "shortcut.tape_stop" }, - { TYPE_KEY_X11, &ssi.tape_pause, "shortcut.tape_pause" }, - { TYPE_KEY_X11, &ssi.tape_record, "shortcut.tape_record" }, - { TYPE_KEY_X11, &ssi.tape_play, "shortcut.tape_play" }, - { TYPE_KEY_X11, &ssi.sound_simple, "shortcut.sound_simple" }, - { TYPE_KEY_X11, &ssi.sound_loops, "shortcut.sound_loops" }, - { TYPE_KEY_X11, &ssi.sound_music, "shortcut.sound_music" }, - { TYPE_KEY_X11, &ssi.snap_left, "shortcut.snap_left" }, - { TYPE_KEY_X11, &ssi.snap_right, "shortcut.snap_right" }, - { TYPE_KEY_X11, &ssi.snap_up, "shortcut.snap_up" }, - { TYPE_KEY_X11, &ssi.snap_down, "shortcut.snap_down" }, -}; + sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y); -static struct TokenInfo player_setup_tokens[] = -{ - { TYPE_BOOLEAN, &sii.use_joystick, ".use_joystick" }, - { TYPE_STRING, &sii.joy.device_name, ".joy.device_name" }, - { TYPE_INTEGER, &sii.joy.xleft, ".joy.xleft" }, - { TYPE_INTEGER, &sii.joy.xmiddle, ".joy.xmiddle" }, - { TYPE_INTEGER, &sii.joy.xright, ".joy.xright" }, - { TYPE_INTEGER, &sii.joy.yupper, ".joy.yupper" }, - { TYPE_INTEGER, &sii.joy.ymiddle, ".joy.ymiddle" }, - { TYPE_INTEGER, &sii.joy.ylower, ".joy.ylower" }, - { TYPE_INTEGER, &sii.joy.snap, ".joy.snap_field" }, - { TYPE_INTEGER, &sii.joy.drop, ".joy.place_bomb" }, - { TYPE_KEY_X11, &sii.key.left, ".key.move_left" }, - { TYPE_KEY_X11, &sii.key.right, ".key.move_right" }, - { TYPE_KEY_X11, &sii.key.up, ".key.move_up" }, - { TYPE_KEY_X11, &sii.key.down, ".key.move_down" }, - { TYPE_KEY_X11, &sii.key.snap, ".key.snap_field" }, - { TYPE_KEY_X11, &sii.key.drop, ".key.place_bomb" }, -}; + char *value_string = getHashEntry(setup_file_hash, token_string); -static struct TokenInfo system_setup_tokens[] = -{ - { TYPE_STRING, &syi.sdl_videodriver, "system.sdl_videodriver" }, - { TYPE_STRING, &syi.sdl_audiodriver, "system.sdl_audiodriver" }, - { TYPE_INTEGER, &syi.audio_fragment_size,"system.audio_fragment_size" }, -}; + if (value_string == NULL) + continue; -static struct TokenInfo internal_setup_tokens[] = -{ - { TYPE_STRING, &sxi.program_title, "program_title" }, - { TYPE_STRING, &sxi.program_version, "program_version" }, - { TYPE_STRING, &sxi.program_author, "program_author" }, - { TYPE_STRING, &sxi.program_email, "program_email" }, - { TYPE_STRING, &sxi.program_website, "program_website" }, - { TYPE_STRING, &sxi.program_copyright, "program_copyright" }, - { TYPE_STRING, &sxi.program_company, "program_company" }, - { TYPE_STRING, &sxi.program_icon_file, "program_icon_file" }, - { TYPE_STRING, &sxi.default_graphics_set, "default_graphics_set" }, - { TYPE_STRING, &sxi.default_sounds_set, "default_sounds_set" }, - { TYPE_STRING, &sxi.default_music_set, "default_music_set" }, - { TYPE_STRING, &sxi.fallback_graphics_file, "fallback_graphics_file"}, - { TYPE_STRING, &sxi.fallback_sounds_file, "fallback_sounds_file" }, - { TYPE_STRING, &sxi.fallback_music_file, "fallback_music_file" }, - { TYPE_STRING, &sxi.default_level_series, "default_level_series" }, - { TYPE_BOOLEAN,&sxi.choose_from_top_leveldir, "choose_from_top_leveldir" }, - { TYPE_BOOLEAN,&sxi.show_scaling_in_title, "show_scaling_in_title" }, - { TYPE_INTEGER,&sxi.default_window_width, "default_window_width" }, - { TYPE_INTEGER,&sxi.default_window_height, "default_window_height" }, -}; + for (x = 0; x < grid_xsize; x++) + { + char c = value_string[x]; -static struct TokenInfo debug_setup_tokens[] = -{ - { TYPE_INTEGER, &sdi.frame_delay[0], "debug.frame_delay_0" }, - { TYPE_INTEGER, &sdi.frame_delay[1], "debug.frame_delay_1" }, - { TYPE_INTEGER, &sdi.frame_delay[2], "debug.frame_delay_2" }, - { TYPE_INTEGER, &sdi.frame_delay[3], "debug.frame_delay_3" }, - { TYPE_INTEGER, &sdi.frame_delay[4], "debug.frame_delay_4" }, - { TYPE_INTEGER, &sdi.frame_delay[5], "debug.frame_delay_5" }, - { TYPE_INTEGER, &sdi.frame_delay[6], "debug.frame_delay_6" }, - { TYPE_INTEGER, &sdi.frame_delay[7], "debug.frame_delay_7" }, - { TYPE_INTEGER, &sdi.frame_delay[8], "debug.frame_delay_8" }, - { TYPE_INTEGER, &sdi.frame_delay[9], "debug.frame_delay_9" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[0], "debug.key.frame_delay_0" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[1], "debug.key.frame_delay_1" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[2], "debug.key.frame_delay_2" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[3], "debug.key.frame_delay_3" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[4], "debug.key.frame_delay_4" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[5], "debug.key.frame_delay_5" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[6], "debug.key.frame_delay_6" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[7], "debug.key.frame_delay_7" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[8], "debug.key.frame_delay_8" }, - { TYPE_KEY_X11, &sdi.frame_delay_key[9], "debug.key.frame_delay_9" }, - { TYPE_BOOLEAN, &sdi.frame_delay_use_mod_key,"debug.frame_delay.use_mod_key"}, - { TYPE_BOOLEAN, &sdi.frame_delay_game_only, "debug.frame_delay.game_only" }, - { TYPE_BOOLEAN, &sdi.show_frames_per_second, "debug.show_frames_per_second" }, -}; + setup.touch.grid_button[i][x][y] = + (c == '.' ? CHAR_GRID_BUTTON_NONE : c); + } + } + } -static struct TokenInfo options_setup_tokens[] = -{ - { TYPE_BOOLEAN, &soi.verbose, "options.verbose" }, -}; + for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, editor_setup_tokens, i); -static char *get_corrected_login_name(char *login_name) -{ - // needed because player name must be a fixed length string - char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1); + for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, shortcut_setup_tokens, i); - strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN); - login_name_new[MAX_PLAYER_NAME_LEN] = '\0'; + for (pnr = 0; pnr < MAX_PLAYERS; pnr++) + { + char prefix[30]; - if (strlen(login_name) > MAX_PLAYER_NAME_LEN) // name has been cut - if (strchr(login_name_new, ' ')) - *strchr(login_name_new, ' ') = '\0'; + sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1); - return login_name_new; -} + setup_input = setup.input[pnr]; + for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++) + { + char full_token[100]; -static void setSetupInfoToDefaults(struct SetupInfo *si) -{ - int i; + sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text); + setSetupInfoFromTokenText(setup_file_hash, player_setup_tokens, i, + full_token); + } + setup.input[pnr] = setup_input; + } - si->player_name = get_corrected_login_name(getLoginName()); + for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, system_setup_tokens, i); - si->sound = TRUE; - si->sound_loops = TRUE; - si->sound_music = TRUE; - si->sound_simple = TRUE; - si->toons = TRUE; - si->scroll_delay = TRUE; - si->scroll_delay_value = STD_SCROLL_DELAY; - si->engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_DEFAULT); - si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; - si->fade_screens = TRUE; - si->autorecord = TRUE; - si->show_titlescreen = TRUE; - si->quick_doors = FALSE; - si->team_mode = FALSE; - si->handicap = TRUE; - si->skip_levels = TRUE; - si->increment_levels = TRUE; - si->auto_play_next_level = TRUE; - si->skip_scores_after_game = FALSE; - si->time_limit = TRUE; - si->fullscreen = FALSE; - si->window_scaling_percent = STD_WINDOW_SCALING_PERCENT; - si->window_scaling_quality = getStringCopy(SCALING_QUALITY_DEFAULT); - si->screen_rendering_mode = getStringCopy(STR_SPECIAL_RENDERING_DEFAULT); - si->vsync_mode = getStringCopy(STR_VSYNC_MODE_DEFAULT); - si->ask_on_escape = TRUE; - si->ask_on_escape_editor = TRUE; - si->ask_on_game_over = TRUE; - si->quick_switch = FALSE; - si->input_on_focus = FALSE; - si->prefer_aga_graphics = TRUE; - si->game_speed_extended = FALSE; - si->game_frame_delay = GAME_FRAME_DELAY; - si->sp_show_border_elements = FALSE; - si->small_game_graphics = FALSE; - si->show_snapshot_buttons = FALSE; + for (i = 0; i < ARRAY_SIZE(internal_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, internal_setup_tokens, i); + + for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, debug_setup_tokens, i); - si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); - si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); - si->music_set = getStringCopy(MUS_CLASSIC_SUBDIR); + for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) + setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i); - si->override_level_graphics = FALSE; - si->override_level_sounds = FALSE; - si->override_level_music = FALSE; + setHideRelatedSetupEntries(); +} - si->volume_simple = 100; // percent - si->volume_loops = 100; // percent - si->volume_music = 100; // percent +static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash) +{ + int i; - si->network_mode = FALSE; - si->network_player_nr = 0; // first player - si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT); + if (!setup_file_hash) + return; - si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT); - si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT; // percent - si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT; // percent - si->touch.transparency = TOUCH_TRANSPARENCY_DEFAULT; // percent - si->touch.draw_outlined = TRUE; - si->touch.draw_pressed = TRUE; + for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) + setSetupInfo(auto_setup_tokens, i, + getHashEntry(setup_file_hash, + auto_setup_tokens[i].text)); +} - for (i = 0; i < 2; i++) - { - char *default_grid_button[6][2] = - { - { " ", " ^^ " }, - { " ", " ^^ " }, - { " ", "<< >>" }, - { " ", "<< >>" }, - { "111222", " vv " }, - { "111222", " vv " } - }; - int grid_xsize = DEFAULT_GRID_XSIZE(i); - int grid_ysize = DEFAULT_GRID_YSIZE(i); - int min_xsize = MIN(6, grid_xsize); - int min_ysize = MIN(6, grid_ysize); - int startx = grid_xsize - min_xsize; - int starty = grid_ysize - min_ysize; - int x, y; +static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash) +{ + int i; - // virtual buttons grid can only be set to defaults if video is initialized - // (this will be repeated if virtual buttons are not loaded from setup file) - if (video.initialized) - { - si->touch.grid_xsize[i] = grid_xsize; - si->touch.grid_ysize[i] = grid_ysize; - } - else - { - si->touch.grid_xsize[i] = -1; - si->touch.grid_ysize[i] = -1; - } + if (!setup_file_hash) + return; - for (x = 0; x < MAX_GRID_XSIZE; x++) - for (y = 0; y < MAX_GRID_YSIZE; y++) - si->touch.grid_button[i][x][y] = CHAR_GRID_BUTTON_NONE; + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + setSetupInfo(server_setup_tokens, i, + getHashEntry(setup_file_hash, + server_setup_tokens[i].text)); +} - for (x = 0; x < min_xsize; x++) - for (y = 0; y < min_ysize; y++) - si->touch.grid_button[i][x][starty + y] = - default_grid_button[y][0][x]; +static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) +{ + int i; - for (x = 0; x < min_xsize; x++) - for (y = 0; y < min_ysize; y++) - si->touch.grid_button[i][startx + x][starty + y] = - default_grid_button[y][1][x]; - } + if (!setup_file_hash) + return; - si->touch.grid_initialized = video.initialized; + for (i = 0; i < ARRAY_SIZE(editor_cascade_setup_tokens); i++) + setSetupInfo(editor_cascade_setup_tokens, i, + getHashEntry(setup_file_hash, + editor_cascade_setup_tokens[i].text)); +} - si->editor.el_boulderdash = TRUE; - si->editor.el_emerald_mine = TRUE; - si->editor.el_emerald_mine_club = TRUE; - si->editor.el_more = TRUE; - si->editor.el_sokoban = TRUE; - si->editor.el_supaplex = TRUE; - si->editor.el_diamond_caves = TRUE; - si->editor.el_dx_boulderdash = TRUE; +void LoadUserNames(void) +{ + int last_user_nr = user.nr; + int i; - si->editor.el_mirror_magic = TRUE; - si->editor.el_deflektor = TRUE; + if (global.user_names != NULL) + { + for (i = 0; i < MAX_PLAYER_NAMES; i++) + checked_free(global.user_names[i]); - si->editor.el_chars = TRUE; - si->editor.el_steel_chars = TRUE; + checked_free(global.user_names); + } - si->editor.el_classic = TRUE; - si->editor.el_custom = TRUE; + global.user_names = checked_calloc(MAX_PLAYER_NAMES * sizeof(char *)); - si->editor.el_user_defined = FALSE; - si->editor.el_dynamic = TRUE; + for (i = 0; i < MAX_PLAYER_NAMES; i++) + { + user.nr = i; - si->editor.el_headlines = TRUE; + SetupFileHash *setup_file_hash = loadSetupFileHash(getSetupFilename()); - si->editor.show_element_token = FALSE; + if (setup_file_hash) + { + char *player_name = getHashEntry(setup_file_hash, "player_name"); - si->editor.use_template_for_new_levels = TRUE; + global.user_names[i] = getFixedUserName(player_name); - si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; - si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; - si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; + freeSetupFileHash(setup_file_hash); + } - si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; - si->shortcut.focus_player[1] = DEFAULT_KEY_FOCUS_PLAYER_2; - si->shortcut.focus_player[2] = DEFAULT_KEY_FOCUS_PLAYER_3; - si->shortcut.focus_player[3] = DEFAULT_KEY_FOCUS_PLAYER_4; - si->shortcut.focus_player_all = DEFAULT_KEY_FOCUS_PLAYER_ALL; + if (global.user_names[i] == NULL) + global.user_names[i] = getStringCopy(getDefaultUserName(i)); + } - si->shortcut.tape_eject = DEFAULT_KEY_TAPE_EJECT; - si->shortcut.tape_extra = DEFAULT_KEY_TAPE_EXTRA; - si->shortcut.tape_stop = DEFAULT_KEY_TAPE_STOP; - si->shortcut.tape_pause = DEFAULT_KEY_TAPE_PAUSE; - si->shortcut.tape_record = DEFAULT_KEY_TAPE_RECORD; - si->shortcut.tape_play = DEFAULT_KEY_TAPE_PLAY; + user.nr = last_user_nr; +} - si->shortcut.sound_simple = DEFAULT_KEY_SOUND_SIMPLE; - si->shortcut.sound_loops = DEFAULT_KEY_SOUND_LOOPS; - si->shortcut.sound_music = DEFAULT_KEY_SOUND_MUSIC; +void LoadSetupFromFilename(char *filename) +{ + SetupFileHash *setup_file_hash = loadSetupFileHash(filename); - si->shortcut.snap_left = DEFAULT_KEY_SNAP_LEFT; - si->shortcut.snap_right = DEFAULT_KEY_SNAP_RIGHT; - si->shortcut.snap_up = DEFAULT_KEY_SNAP_UP; - si->shortcut.snap_down = DEFAULT_KEY_SNAP_DOWN; + if (setup_file_hash) + { + decodeSetupFileHash_Default(setup_file_hash); - for (i = 0; i < MAX_PLAYERS; i++) + freeSetupFileHash(setup_file_hash); + } + else { - si->input[i].use_joystick = FALSE; - 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; - si->input[i].joy.yupper = JOYSTICK_YUPPER; - si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE; - si->input[i].joy.ylower = JOYSTICK_YLOWER; - si->input[i].joy.snap = (i == 0 ? JOY_BUTTON_1 : 0); - si->input[i].joy.drop = (i == 0 ? JOY_BUTTON_2 : 0); - si->input[i].key.left = (i == 0 ? DEFAULT_KEY_LEFT : KSYM_UNDEFINED); - si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED); - si->input[i].key.up = (i == 0 ? DEFAULT_KEY_UP : KSYM_UNDEFINED); - si->input[i].key.down = (i == 0 ? DEFAULT_KEY_DOWN : KSYM_UNDEFINED); - si->input[i].key.snap = (i == 0 ? DEFAULT_KEY_SNAP : KSYM_UNDEFINED); - si->input[i].key.drop = (i == 0 ? DEFAULT_KEY_DROP : KSYM_UNDEFINED); + Debug("setup", "using default setup values"); } +} - si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT); - si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT); - si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE; +static void LoadSetup_SpecialPostProcessing(void) +{ + char *player_name_new; - si->internal.program_title = getStringCopy(PROGRAM_TITLE_STRING); - si->internal.program_version = getStringCopy(getProgramRealVersionString()); - si->internal.program_author = getStringCopy(PROGRAM_AUTHOR_STRING); - si->internal.program_email = getStringCopy(PROGRAM_EMAIL_STRING); - si->internal.program_website = getStringCopy(PROGRAM_WEBSITE_STRING); - si->internal.program_copyright = getStringCopy(PROGRAM_COPYRIGHT_STRING); - si->internal.program_company = getStringCopy(PROGRAM_COMPANY_STRING); + // needed to work around problems with fixed length strings + player_name_new = getFixedUserName(setup.player_name); + free(setup.player_name); + setup.player_name = player_name_new; - si->internal.program_icon_file = getStringCopy(PROGRAM_ICON_FILENAME); + // "scroll_delay: on(3) / off(0)" was replaced by scroll delay value + if (setup.scroll_delay == FALSE) + { + setup.scroll_delay_value = MIN_SCROLL_DELAY; + setup.scroll_delay = TRUE; // now always "on" + } - si->internal.default_graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); - si->internal.default_sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); - si->internal.default_music_set = getStringCopy(MUS_CLASSIC_SUBDIR); + // make sure that scroll delay value stays inside valid range + setup.scroll_delay_value = + MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY); +} - si->internal.fallback_graphics_file = getStringCopy(UNDEFINED_FILENAME); - si->internal.fallback_sounds_file = getStringCopy(UNDEFINED_FILENAME); - si->internal.fallback_music_file = getStringCopy(UNDEFINED_FILENAME); +void LoadSetup_Default(void) +{ + char *filename; - si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET); - si->internal.choose_from_top_leveldir = FALSE; - si->internal.show_scaling_in_title = TRUE; + // always start with reliable default values + setSetupInfoToDefaults(&setup); - si->internal.default_window_width = WIN_XSIZE_DEFAULT; - si->internal.default_window_height = WIN_YSIZE_DEFAULT; + // try to load setup values from default setup file + filename = getDefaultSetupFilename(); - si->debug.frame_delay[0] = DEFAULT_FRAME_DELAY_0; - si->debug.frame_delay[1] = DEFAULT_FRAME_DELAY_1; - si->debug.frame_delay[2] = DEFAULT_FRAME_DELAY_2; - si->debug.frame_delay[3] = DEFAULT_FRAME_DELAY_3; - si->debug.frame_delay[4] = DEFAULT_FRAME_DELAY_4; - si->debug.frame_delay[5] = DEFAULT_FRAME_DELAY_5; - si->debug.frame_delay[6] = DEFAULT_FRAME_DELAY_6; - si->debug.frame_delay[7] = DEFAULT_FRAME_DELAY_7; - si->debug.frame_delay[8] = DEFAULT_FRAME_DELAY_8; - si->debug.frame_delay[9] = DEFAULT_FRAME_DELAY_9; + if (fileExists(filename)) + LoadSetupFromFilename(filename); - si->debug.frame_delay_key[0] = DEFAULT_KEY_FRAME_DELAY_0; - si->debug.frame_delay_key[1] = DEFAULT_KEY_FRAME_DELAY_1; - si->debug.frame_delay_key[2] = DEFAULT_KEY_FRAME_DELAY_2; - si->debug.frame_delay_key[3] = DEFAULT_KEY_FRAME_DELAY_3; - si->debug.frame_delay_key[4] = DEFAULT_KEY_FRAME_DELAY_4; - si->debug.frame_delay_key[5] = DEFAULT_KEY_FRAME_DELAY_5; - si->debug.frame_delay_key[6] = DEFAULT_KEY_FRAME_DELAY_6; - si->debug.frame_delay_key[7] = DEFAULT_KEY_FRAME_DELAY_7; - si->debug.frame_delay_key[8] = DEFAULT_KEY_FRAME_DELAY_8; - si->debug.frame_delay_key[9] = DEFAULT_KEY_FRAME_DELAY_9; + // try to load setup values from platform setup file + filename = getPlatformSetupFilename(); - si->debug.frame_delay_use_mod_key = DEFAULT_FRAME_DELAY_USE_MOD_KEY; - si->debug.frame_delay_game_only = DEFAULT_FRAME_DELAY_GAME_ONLY; + if (fileExists(filename)) + LoadSetupFromFilename(filename); - si->debug.show_frames_per_second = FALSE; + // try to load setup values from user setup file + filename = getSetupFilename(); - si->options.verbose = FALSE; + LoadSetupFromFilename(filename); + + LoadSetup_SpecialPostProcessing(); +} + +void LoadSetup_AutoSetup(void) +{ + char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + SetupFileHash *setup_file_hash = NULL; + + // always start with reliable default values + setSetupInfoToDefaults_AutoSetup(&setup); + + setup_file_hash = loadSetupFileHash(filename); + + if (setup_file_hash) + { + decodeSetupFileHash_AutoSetup(setup_file_hash); + + freeSetupFileHash(setup_file_hash); + } -#if defined(PLATFORM_ANDROID) - si->fullscreen = TRUE; -#endif + free(filename); } -static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) +void LoadSetup_ServerSetup(void) { - si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE; -} + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + SetupFileHash *setup_file_hash = NULL; -static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) -{ - si->editor_cascade.el_bd = TRUE; - si->editor_cascade.el_em = TRUE; - si->editor_cascade.el_emc = TRUE; - si->editor_cascade.el_rnd = TRUE; - si->editor_cascade.el_sb = TRUE; - si->editor_cascade.el_sp = TRUE; - si->editor_cascade.el_dc = TRUE; - si->editor_cascade.el_dx = TRUE; + // always start with reliable default values + setSetupInfoToDefaults_ServerSetup(&setup); - si->editor_cascade.el_mm = TRUE; - si->editor_cascade.el_df = TRUE; + setup_file_hash = loadSetupFileHash(filename); - si->editor_cascade.el_chars = FALSE; - si->editor_cascade.el_steel_chars = FALSE; - si->editor_cascade.el_ce = FALSE; - si->editor_cascade.el_ge = FALSE; - si->editor_cascade.el_ref = FALSE; - si->editor_cascade.el_user = FALSE; - si->editor_cascade.el_dynamic = FALSE; -} + if (setup_file_hash) + { + decodeSetupFileHash_ServerSetup(setup_file_hash); -#define MAX_HIDE_SETUP_TOKEN_SIZE 20 + freeSetupFileHash(setup_file_hash); + } -static char *getHideSetupToken(void *setup_value) -{ - static char hide_setup_token[MAX_HIDE_SETUP_TOKEN_SIZE]; + free(filename); - if (setup_value != NULL) - snprintf(hide_setup_token, MAX_HIDE_SETUP_TOKEN_SIZE, "%p", setup_value); + if (setup.player_uuid == NULL) + { + // player UUID does not yet exist in setup file + setup.player_uuid = getStringCopy(getUUID()); + setup.player_version = 2; - return hide_setup_token; + SaveSetup_ServerSetup(); + } } -void setHideSetupEntry(void *setup_value) +void LoadSetup_EditorCascade(void) { - char *hide_setup_token = getHideSetupToken(setup_value); + char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); + SetupFileHash *setup_file_hash = NULL; - if (setup_value != NULL) - setHashEntry(hide_setup_hash, hide_setup_token, ""); -} + // always start with reliable default values + setSetupInfoToDefaults_EditorCascade(&setup); -static void setHideSetupEntryRaw(char *token_text, void *setup_value_raw) -{ - // !!! DIRTY WORKAROUND; TO BE FIXED AFTER THE MM ENGINE RELEASE !!! - void *setup_value = setup_value_raw - (void *)&si + (void *)&setup; + setup_file_hash = loadSetupFileHash(filename); + + if (setup_file_hash) + { + decodeSetupFileHash_EditorCascade(setup_file_hash); + + freeSetupFileHash(setup_file_hash); + } - setHideSetupEntry(setup_value); + free(filename); } -boolean hideSetupEntry(void *setup_value) +void LoadSetup(void) { - char *hide_setup_token = getHideSetupToken(setup_value); - - return (setup_value != NULL && - getHashEntry(hide_setup_hash, hide_setup_token) != NULL); + LoadSetup_Default(); + LoadSetup_AutoSetup(); + LoadSetup_ServerSetup(); + LoadSetup_EditorCascade(); } -static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash, - struct TokenInfo *token_info, - int token_nr, char *token_text) +static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, + char *mapping_line) { - char *token_hide_text = getStringCat2(token_text, ".hide"); - char *token_hide_value = getHashEntry(setup_file_hash, token_hide_text); + char mapping_guid[MAX_LINE_LEN]; + char *mapping_start, *mapping_end; - // set the value of this setup option in the setup option structure - setSetupInfo(token_info, token_nr, getHashEntry(setup_file_hash, token_text)); + // get GUID from game controller mapping line: copy complete line + strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1); + mapping_guid[MAX_LINE_LEN - 1] = '\0'; - // check if this setup option should be hidden in the setup menu - if (token_hide_value != NULL && get_boolean_from_string(token_hide_value)) - setHideSetupEntryRaw(token_text, token_info[token_nr].value); + // get GUID from game controller mapping line: cut after GUID part + mapping_start = strchr(mapping_guid, ','); + if (mapping_start != NULL) + *mapping_start = '\0'; + + // cut newline from game controller mapping line + mapping_end = strchr(mapping_line, '\n'); + if (mapping_end != NULL) + *mapping_end = '\0'; + + // add mapping entry to game controller mappings hash + setHashEntry(mappings_hash, mapping_guid, mapping_line); } -static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, - struct TokenInfo *token_info, - int token_nr) +static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, + char *filename) { - setSetupInfoFromTokenText(setup_file_hash, token_info, token_nr, - token_info[token_nr].text); + FILE *file; + + if (!(file = fopen(filename, MODE_READ))) + { + Warn("cannot read game controller mappings file '%s'", filename); + + return; + } + + while (!feof(file)) + { + char line[MAX_LINE_LEN]; + + if (!fgets(line, MAX_LINE_LEN, file)) + break; + + addGameControllerMappingToHash(mappings_hash, line); + } + + fclose(file); } -static void decodeSetupFileHash(SetupFileHash *setup_file_hash) +void SaveSetup_Default(void) { + char *filename = getSetupFilename(); + FILE *file; int i, pnr; - if (!setup_file_hash) + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write setup file '%s'", filename); + return; + } - if (hide_setup_hash == NULL) - hide_setup_hash = newSetupFileHash(); + fprintFileHeader(file, SETUP_FILENAME); - // global setup - si = setup; - for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i); - setup = si; + for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++) + { + // just to make things nicer :) + if (global_setup_tokens[i].value == &setup.multiple_users || + global_setup_tokens[i].value == &setup.sound || + global_setup_tokens[i].value == &setup.graphics_set || + global_setup_tokens[i].value == &setup.volume_simple || + global_setup_tokens[i].value == &setup.network_mode || + global_setup_tokens[i].value == &setup.touch.control_type || + global_setup_tokens[i].value == &setup.touch.grid_xsize[0] || + global_setup_tokens[i].value == &setup.touch.grid_xsize[1]) + fprintf(file, "\n"); + + fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i)); + } - // virtual buttons setup - setup.touch.grid_initialized = TRUE; for (i = 0; i < 2; i++) { int grid_xsize = setup.touch.grid_xsize[i]; int grid_ysize = setup.touch.grid_ysize[i]; int x, y; - // if virtual buttons are not loaded from setup file, repeat initializing - // virtual buttons grid with default values later when video is initialized - if (grid_xsize == -1 || - grid_ysize == -1) - { - setup.touch.grid_initialized = FALSE; - - continue; - } + fprintf(file, "\n"); for (y = 0; y < grid_ysize; y++) { char token_string[MAX_LINE_LEN]; + char value_string[MAX_LINE_LEN]; sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y); - char *value_string = getHashEntry(setup_file_hash, token_string); - - if (value_string == NULL) - continue; - for (x = 0; x < grid_xsize; x++) { - char c = value_string[x]; + char c = setup.touch.grid_button[i][x][y]; - setup.touch.grid_button[i][x][y] = - (c == '.' ? CHAR_GRID_BUTTON_NONE : c); + value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c); } + + value_string[grid_xsize] = '\0'; + + fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string)); } } - // editor setup - sei = setup.editor; - for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, editor_setup_tokens, i); - setup.editor = sei; + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i)); + + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i)); + + for (pnr = 0; pnr < MAX_PLAYERS; pnr++) + { + char prefix[30]; + + sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1); + fprintf(file, "\n"); + + setup_input = setup.input[pnr]; + for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i)); + } + + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i)); + + // (internal setup values not saved to user setup file) + + 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) + fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); + + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} + +void SaveSetup_AutoSetup(void) +{ + char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + FILE *file; + int i; + + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write auto setup file '%s'", filename); + + free(filename); + + return; + } + + fprintFileHeader(file, AUTOSETUP_FILENAME); + + for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); + + free(filename); +} + +void SaveSetup_ServerSetup(void) +{ + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + FILE *file; + int i; + + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write server setup file '%s'", filename); + + free(filename); + + return; + } + + fprintFileHeader(file, SERVERSETUP_FILENAME); + + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + { + // just to make things nicer :) + if (server_setup_tokens[i].value == &setup.use_api_server) + fprintf(file, "\n"); + + fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i)); + } + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); + + free(filename); +} + +void SaveSetup_EditorCascade(void) +{ + char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); + FILE *file; + int i; - // shortcut setup - ssi = setup.shortcut; - for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, shortcut_setup_tokens, i); - setup.shortcut = ssi; + InitUserDataDirectory(); - // player setup - for (pnr = 0; pnr < MAX_PLAYERS; pnr++) + if (!(file = fopen(filename, MODE_WRITE))) { - char prefix[30]; + Warn("cannot write editor cascade state file '%s'", filename); - sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1); - - sii = setup.input[pnr]; - for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++) - { - char full_token[100]; + free(filename); - sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text); - setSetupInfoFromTokenText(setup_file_hash, player_setup_tokens, i, - full_token); - } - setup.input[pnr] = sii; + return; } - // system setup - syi = setup.system; - for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, system_setup_tokens, i); - setup.system = syi; + fprintFileHeader(file, EDITORCASCADE_FILENAME); - // internal setup - sxi = setup.internal; - for (i = 0; i < NUM_INTERNAL_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, internal_setup_tokens, i); - setup.internal = sxi; + for (i = 0; i < ARRAY_SIZE(editor_cascade_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(editor_cascade_setup_tokens, "", i)); - // debug setup - sdi = setup.debug; - for (i = 0; i < NUM_DEBUG_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, debug_setup_tokens, i); - setup.debug = sdi; + fclose(file); - // options setup - soi = setup.options; - for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++) - setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i); - setup.options = soi; + SetFilePermissions(filename, PERMS_PRIVATE); - setHideRelatedSetupEntries(); + free(filename); } -static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash) +void SaveSetup(void) { - int i; + SaveSetup_Default(); + SaveSetup_AutoSetup(); + SaveSetup_ServerSetup(); + SaveSetup_EditorCascade(); +} + +static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, + char *filename) +{ + FILE *file; + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write game controller mappings file '%s'", filename); - if (!setup_file_hash) return; + } - // auto setup - sasi = setup.auto_setup; - for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++) - setSetupInfo(auto_setup_tokens, i, - getHashEntry(setup_file_hash, - auto_setup_tokens[i].text)); - setup.auto_setup = sasi; + BEGIN_HASH_ITERATION(mappings_hash, itr) + { + fprintf(file, "%s\n", HASH_ITERATION_VALUE(itr)); + } + END_HASH_ITERATION(mappings_hash, itr) + + fclose(file); } -static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) +void SaveSetup_AddGameControllerMapping(char *mapping) { - int i; + char *filename = getPath2(getSetupDir(), GAMECONTROLLER_BASENAME); + SetupFileHash *mappings_hash = newSetupFileHash(); - if (!setup_file_hash) - return; + InitUserDataDirectory(); - // editor cascade setup - seci = setup.editor_cascade; - for (i = 0; i < NUM_EDITOR_CASCADE_SETUP_TOKENS; i++) - setSetupInfo(editor_cascade_setup_tokens, i, - getHashEntry(setup_file_hash, - editor_cascade_setup_tokens[i].text)); - setup.editor_cascade = seci; + // load existing personal game controller mappings + LoadSetup_ReadGameControllerMappings(mappings_hash, filename); + + // add new mapping to personal game controller mappings + addGameControllerMappingToHash(mappings_hash, mapping); + + // save updated personal game controller mappings + SaveSetup_WriteGameControllerMappings(mappings_hash, filename); + + freeSetupFileHash(mappings_hash); + free(filename); } -void LoadSetupFromFilename(char *filename) +void LoadCustomElementDescriptions(void) { - SetupFileHash *setup_file_hash = loadSetupFileHash(filename); + char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS); + SetupFileHash *setup_file_hash; + int i; - if (setup_file_hash) + for (i = 0; i < NUM_FILE_ELEMENTS; i++) { - decodeSetupFileHash(setup_file_hash); - - freeSetupFileHash(setup_file_hash); + if (element_info[i].custom_description != NULL) + { + free(element_info[i].custom_description); + element_info[i].custom_description = NULL; + } } - else + + if ((setup_file_hash = loadSetupFileHash(filename)) == NULL) + return; + + for (i = 0; i < NUM_FILE_ELEMENTS; i++) { - Error(ERR_DEBUG, "using default setup values"); + char *token = getStringCat2(element_info[i].token_name, ".name"); + char *value = getHashEntry(setup_file_hash, token); + + if (value != NULL) + element_info[i].custom_description = getStringCopy(value); + + free(token); } + + freeSetupFileHash(setup_file_hash); } -static void LoadSetup_SpecialPostProcessing(void) +static int getElementFromToken(char *token) { - char *player_name_new; + char *value = getHashEntry(element_token_hash, token); - // needed to work around problems with fixed length strings - player_name_new = get_corrected_login_name(setup.player_name); - free(setup.player_name); - setup.player_name = player_name_new; + if (value != NULL) + return atoi(value); - // "scroll_delay: on(3) / off(0)" was replaced by scroll delay value - if (setup.scroll_delay == FALSE) - { - setup.scroll_delay_value = MIN_SCROLL_DELAY; - setup.scroll_delay = TRUE; // now always "on" - } + Warn("unknown element token '%s'", token); - // make sure that scroll delay value stays inside valid range - setup.scroll_delay_value = - MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY); + return EL_UNDEFINED; } -void LoadSetup(void) +void FreeGlobalAnimEventInfo(void) { - char *filename; - - // always start with reliable default values - setSetupInfoToDefaults(&setup); + struct GlobalAnimEventInfo *gaei = &global_anim_event_info; - // try to load setup values from default setup file - filename = getDefaultSetupFilename(); + if (gaei->event_list == NULL) + return; - if (fileExists(filename)) - LoadSetupFromFilename(filename); + int i; - // try to load setup values from user setup file - filename = getSetupFilename(); + for (i = 0; i < gaei->num_event_lists; i++) + { + checked_free(gaei->event_list[i]->event_value); + checked_free(gaei->event_list[i]); + } - LoadSetupFromFilename(filename); + checked_free(gaei->event_list); - LoadSetup_SpecialPostProcessing(); + gaei->event_list = NULL; + gaei->num_event_lists = 0; } -void LoadSetup_AutoSetup(void) +static int AddGlobalAnimEventList(void) { - char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); - SetupFileHash *setup_file_hash = NULL; + struct GlobalAnimEventInfo *gaei = &global_anim_event_info; + int list_pos = gaei->num_event_lists++; - // always start with reliable default values - setSetupInfoToDefaults_AutoSetup(&setup); + gaei->event_list = checked_realloc(gaei->event_list, gaei->num_event_lists * + sizeof(struct GlobalAnimEventListInfo *)); - setup_file_hash = loadSetupFileHash(filename); + gaei->event_list[list_pos] = + checked_calloc(sizeof(struct GlobalAnimEventListInfo)); - if (setup_file_hash) - { - decodeSetupFileHash_AutoSetup(setup_file_hash); + struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos]; - freeSetupFileHash(setup_file_hash); - } + gaeli->event_value = NULL; + gaeli->num_event_values = 0; - free(filename); + return list_pos; } -void LoadSetup_EditorCascade(void) +static int AddGlobalAnimEventValue(int list_pos, int event_value) { - char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); - SetupFileHash *setup_file_hash = NULL; + // do not add empty global animation events + if (event_value == ANIM_EVENT_NONE) + return list_pos; - // always start with reliable default values - setSetupInfoToDefaults_EditorCascade(&setup); + // if list position is undefined, create new list + if (list_pos == ANIM_EVENT_UNDEFINED) + list_pos = AddGlobalAnimEventList(); - setup_file_hash = loadSetupFileHash(filename); + struct GlobalAnimEventInfo *gaei = &global_anim_event_info; + struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos]; + int value_pos = gaeli->num_event_values++; - if (setup_file_hash) - { - decodeSetupFileHash_EditorCascade(setup_file_hash); + gaeli->event_value = checked_realloc(gaeli->event_value, + gaeli->num_event_values * sizeof(int *)); - freeSetupFileHash(setup_file_hash); - } + gaeli->event_value[value_pos] = event_value; - free(filename); + return list_pos; } -static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, - char *mapping_line) +int GetGlobalAnimEventValue(int list_pos, int value_pos) { - char mapping_guid[MAX_LINE_LEN]; - char *mapping_start, *mapping_end; + if (list_pos == ANIM_EVENT_UNDEFINED) + return ANIM_EVENT_NONE; - // get GUID from game controller mapping line: copy complete line - strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1); - mapping_guid[MAX_LINE_LEN - 1] = '\0'; + struct GlobalAnimEventInfo *gaei = &global_anim_event_info; + struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos]; - // get GUID from game controller mapping line: cut after GUID part - mapping_start = strchr(mapping_guid, ','); - if (mapping_start != NULL) - *mapping_start = '\0'; + return gaeli->event_value[value_pos]; +} - // cut newline from game controller mapping line - mapping_end = strchr(mapping_line, '\n'); - if (mapping_end != NULL) - *mapping_end = '\0'; +int GetGlobalAnimEventValueCount(int list_pos) +{ + if (list_pos == ANIM_EVENT_UNDEFINED) + return 0; - // add mapping entry to game controller mappings hash - setHashEntry(mappings_hash, mapping_guid, mapping_line); + struct GlobalAnimEventInfo *gaei = &global_anim_event_info; + struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos]; + + return gaeli->num_event_values; } -static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, - char *filename) +// This function checks if a string of the format "string1, string2, ..." +// exactly contains a string . + +static boolean string_has_parameter(char *s, char *s_contained) { - FILE *file; + char *substring; - if (!(file = fopen(filename, MODE_READ))) + if (s == NULL || s_contained == NULL) + return FALSE; + + if (strlen(s_contained) > strlen(s)) + return FALSE; + + if (strncmp(s, s_contained, strlen(s_contained)) == 0) { - Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename); + char next_char = s[strlen(s_contained)]; - return; + // check if next character is delimiter or whitespace + if (next_char == ',' || next_char == '\0' || + next_char == ' ' || next_char == '\t') + return TRUE; } - while (!feof(file)) - { - char line[MAX_LINE_LEN]; + // check if string contains another parameter string after a comma + substring = strchr(s, ','); + if (substring == NULL) // string does not contain a comma + return FALSE; - if (!fgets(line, MAX_LINE_LEN, file)) - break; + // advance string pointer to next character after the comma + substring++; - addGameControllerMappingToHash(mappings_hash, line); - } + // skip potential whitespaces after the comma + while (*substring == ' ' || *substring == '\t') + substring++; - fclose(file); + return string_has_parameter(substring, s_contained); } -void SaveSetup(void) +static int get_anim_parameter_value_ce(char *s) { - char *filename = getSetupFilename(); - FILE *file; - int i, pnr; - - InitUserDataDirectory(); - - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot write setup file '%s'", filename); - return; - } + 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; - fprintFileHeader(file, SETUP_FILENAME); + if (matching_char == NULL) + return ANIM_EVENT_NONE; - // global setup - si = setup; - for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++) - { - // just to make things nicer :) - if (i == SETUP_TOKEN_PLAYER_NAME + 1 || - i == SETUP_TOKEN_GRAPHICS_SET || - i == SETUP_TOKEN_VOLUME_SIMPLE || - i == SETUP_TOKEN_NETWORK_MODE || - i == SETUP_TOKEN_TOUCH_CONTROL_TYPE || - i == SETUP_TOKEN_TOUCH_GRID_XSIZE_0 || - i == SETUP_TOKEN_TOUCH_GRID_XSIZE_1) - fprintf(file, "\n"); + result = ANIM_EVENT_CE_CHANGE; - fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i)); - } + s_ptr = matching_char + pattern_1_len; - // virtual buttons setup - for (i = 0; i < 2; i++) + // check for custom element number ("custom_X", "custom_XX" or "custom_XXX") + if (*s_ptr >= '0' && *s_ptr <= '9') { - int grid_xsize = setup.touch.grid_xsize[i]; - int grid_ysize = setup.touch.grid_ysize[i]; - int x, y; - - fprintf(file, "\n"); + int gic_ce_nr = (*s_ptr++ - '0'); - for (y = 0; y < grid_ysize; y++) + if (*s_ptr >= '0' && *s_ptr <= '9') { - char token_string[MAX_LINE_LEN]; - char value_string[MAX_LINE_LEN]; + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); - sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y); - - for (x = 0; x < grid_xsize; x++) - { - char c = setup.touch.grid_button[i][x][y]; + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + } - value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c); - } + if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS) + return ANIM_EVENT_NONE; - value_string[grid_xsize] = '\0'; + // custom element stored as 0 to 255 + gic_ce_nr--; - fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string)); - } + result |= gic_ce_nr << ANIM_EVENT_CE_BIT; } + else + { + // invalid custom element number specified - // editor setup - sei = setup.editor; - fprintf(file, "\n"); - for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i)); - - // shortcut setup - ssi = setup.shortcut; - fprintf(file, "\n"); - for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i)); + return ANIM_EVENT_NONE; + } - // player setup - for (pnr = 0; pnr < MAX_PLAYERS; pnr++) + // check for change page number ("page_X" or "page_XX") (optional) + if (strPrefix(s_ptr, pattern_2)) { - char prefix[30]; - - sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1); - fprintf(file, "\n"); + s_ptr += strlen(pattern_2); - sii = setup.input[pnr]; - for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i)); - } + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_page_nr = (*s_ptr++ - '0'); - // system setup - syi = setup.system; - fprintf(file, "\n"); - for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i)); + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0'); - // internal setup - // (internal setup values not saved to user setup file) + if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES) + return ANIM_EVENT_NONE; - // debug setup - sdi = setup.debug; - fprintf(file, "\n"); - for (i = 0; i < NUM_DEBUG_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); + // change page stored as 1 to 32 (0 means "all change pages") - // options setup - soi = setup.options; - fprintf(file, "\n"); - for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + result |= gic_page_nr << ANIM_EVENT_PAGE_BIT; + } + else + { + // invalid animation part number specified - fclose(file); + return ANIM_EVENT_NONE; + } + } - SetFilePermissions(filename, PERMS_PRIVATE); + // 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; } -void SaveSetup_AutoSetup(void) +static int get_anim_parameter_value(char *s) { - char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); - FILE *file; + int event_value[] = + { + ANIM_EVENT_CLICK, + ANIM_EVENT_INIT, + ANIM_EVENT_START, + ANIM_EVENT_END, + ANIM_EVENT_POST + }; + char *pattern_1[] = + { + "click:anim_", + "init:anim_", + "start:anim_", + "end:anim_", + "post:anim_" + }; + char *pattern_2 = ".part_"; + char *matching_char = NULL; + char *s_ptr = s; + int pattern_1_len = 0; + int result = ANIM_EVENT_NONE; int i; - InitUserDataDirectory(); + result = get_anim_parameter_value_ce(s); - if (!(file = fopen(filename, MODE_WRITE))) + if (result != ANIM_EVENT_NONE) + return result; + + for (i = 0; i < ARRAY_SIZE(event_value); i++) { - Error(ERR_WARN, "cannot write auto setup file '%s'", filename); - free(filename); - return; + matching_char = strstr(s_ptr, pattern_1[i]); + pattern_1_len = strlen(pattern_1[i]); + result = event_value[i]; + + if (matching_char != NULL) + break; } - fprintFileHeader(file, AUTOSETUP_FILENAME); + if (matching_char == NULL) + return ANIM_EVENT_NONE; - sasi = setup.auto_setup; - for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); + s_ptr = matching_char + pattern_1_len; - fclose(file); + // check for main animation number ("anim_X" or "anim_XX") + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_anim_nr = (*s_ptr++ - '0'); - SetFilePermissions(filename, PERMS_PRIVATE); + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_anim_nr = 10 * gic_anim_nr + (*s_ptr++ - '0'); - free(filename); -} + if (gic_anim_nr < 1 || gic_anim_nr > MAX_GLOBAL_ANIMS) + return ANIM_EVENT_NONE; -void SaveSetup_EditorCascade(void) -{ - char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); - FILE *file; - int i; + result |= gic_anim_nr << ANIM_EVENT_ANIM_BIT; + } + else + { + // invalid main animation number specified - InitUserDataDirectory(); + return ANIM_EVENT_NONE; + } - if (!(file = fopen(filename, MODE_WRITE))) + // check for animation part number ("part_X" or "part_XX") (optional) + if (strPrefix(s_ptr, pattern_2)) { - Error(ERR_WARN, "cannot write editor cascade state file '%s'", filename); - free(filename); - return; - } + s_ptr += strlen(pattern_2); - fprintFileHeader(file, EDITORCASCADE_FILENAME); + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_part_nr = (*s_ptr++ - '0'); - seci = setup.editor_cascade; - for (i = 0; i < NUM_EDITOR_CASCADE_SETUP_TOKENS; i++) - fprintf(file, "%s\n", getSetupLine(editor_cascade_setup_tokens, "", i)); + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_part_nr = 10 * gic_part_nr + (*s_ptr++ - '0'); - fclose(file); + if (gic_part_nr < 1 || gic_part_nr > MAX_GLOBAL_ANIM_PARTS) + return ANIM_EVENT_NONE; - SetFilePermissions(filename, PERMS_PRIVATE); + result |= gic_part_nr << ANIM_EVENT_PART_BIT; + } + else + { + // invalid animation part number specified - free(filename); + 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 void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, - char *filename) +static int get_anim_parameter_values(char *s) { - FILE *file; + int list_pos = ANIM_EVENT_UNDEFINED; + int event_value = ANIM_EVENT_DEFAULT; - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename); + if (string_has_parameter(s, "any")) + event_value |= ANIM_EVENT_ANY; - return; - } + if (string_has_parameter(s, "click:self") || + string_has_parameter(s, "click") || + string_has_parameter(s, "self")) + event_value |= ANIM_EVENT_SELF; - BEGIN_HASH_ITERATION(mappings_hash, itr) + if (string_has_parameter(s, "unclick:any")) + event_value |= ANIM_EVENT_UNCLICK_ANY; + + // if animation event found, add it to global animation event list + if (event_value != ANIM_EVENT_NONE) + list_pos = AddGlobalAnimEventValue(list_pos, event_value); + + while (s != NULL) { - fprintf(file, "%s\n", HASH_ITERATION_VALUE(itr)); + // add optional "click:anim_X" or "click:anim_X.part_X" parameter + event_value = get_anim_parameter_value(s); + + // if animation event found, add it to global animation event list + if (event_value != ANIM_EVENT_NONE) + list_pos = AddGlobalAnimEventValue(list_pos, event_value); + + // continue with next part of the string, starting with next comma + s = strchr(s + 1, ','); } - END_HASH_ITERATION(mappings_hash, itr) - fclose(file); + return list_pos; } -void SaveSetup_AddGameControllerMapping(char *mapping) +static int get_anim_action_parameter_value(char *token) { - char *filename = getPath2(getSetupDir(), GAMECONTROLLER_BASENAME); - SetupFileHash *mappings_hash = newSetupFileHash(); + // check most common default case first to massively speed things up + if (strEqual(token, ARG_UNDEFINED)) + return ANIM_EVENT_ACTION_NONE; - InitUserDataDirectory(); + int result = getImageIDFromToken(token); - // load existing personal game controller mappings - LoadSetup_ReadGameControllerMappings(mappings_hash, filename); + if (result == -1) + { + char *gfx_token = getStringCat2("gfx.", token); - // add new mapping to personal game controller mappings - addGameControllerMappingToHash(mappings_hash, mapping); + result = getImageIDFromToken(gfx_token); - // save updated personal game controller mappings - SaveSetup_WriteGameControllerMappings(mappings_hash, filename); + checked_free(gfx_token); + } - freeSetupFileHash(mappings_hash); - free(filename); -} + if (result == -1) + { + Key key = getKeyFromX11KeyName(token); -void LoadCustomElementDescriptions(void) -{ - char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS); - SetupFileHash *setup_file_hash; - int i; + if (key != KSYM_UNDEFINED) + result = -(int)key; + } - for (i = 0; i < NUM_FILE_ELEMENTS; i++) + if (result == -1) { - if (element_info[i].custom_description != NULL) + if (isURL(token)) { - free(element_info[i].custom_description); - element_info[i].custom_description = NULL; + 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 ((setup_file_hash = loadSetupFileHash(filename)) == NULL) - return; + if (result == -1) + result = ANIM_EVENT_ACTION_NONE; - for (i = 0; i < NUM_FILE_ELEMENTS; i++) + return result; +} + +int get_parameter_value(char *value_raw, char *suffix, int type) +{ + char *value = getStringToLower(value_raw); + int result = 0; // probably a save default value + + if (strEqual(suffix, ".direction")) { - char *token = getStringCat2(element_info[i].token_name, ".name"); - char *value = getHashEntry(setup_file_hash, token); + result = (strEqual(value, "left") ? MV_LEFT : + strEqual(value, "right") ? MV_RIGHT : + strEqual(value, "up") ? MV_UP : + strEqual(value, "down") ? MV_DOWN : MV_NONE); + } + else if (strEqual(suffix, ".position")) + { + result = (strEqual(value, "left") ? POS_LEFT : + strEqual(value, "right") ? POS_RIGHT : + strEqual(value, "top") ? POS_TOP : + strEqual(value, "upper") ? POS_UPPER : + strEqual(value, "middle") ? POS_MIDDLE : + 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")) + { + result = (strEqual(value, "left") ? ALIGN_LEFT : + strEqual(value, "right") ? ALIGN_RIGHT : + strEqual(value, "center") ? ALIGN_CENTER : + strEqual(value, "middle") ? ALIGN_CENTER : ALIGN_DEFAULT); + } + else if (strEqual(suffix, ".valign")) + { + result = (strEqual(value, "top") ? VALIGN_TOP : + strEqual(value, "bottom") ? VALIGN_BOTTOM : + strEqual(value, "middle") ? VALIGN_MIDDLE : + strEqual(value, "center") ? VALIGN_MIDDLE : VALIGN_DEFAULT); + } + else if (strEqual(suffix, ".anim_mode")) + { + result = (string_has_parameter(value, "none") ? ANIM_NONE : + string_has_parameter(value, "loop") ? ANIM_LOOP : + string_has_parameter(value, "linear") ? ANIM_LINEAR : + string_has_parameter(value, "pingpong") ? ANIM_PINGPONG : + string_has_parameter(value, "pingpong2") ? ANIM_PINGPONG2 : + string_has_parameter(value, "random") ? ANIM_RANDOM : + string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC : + string_has_parameter(value, "ce_value") ? ANIM_CE_VALUE : + string_has_parameter(value, "ce_score") ? ANIM_CE_SCORE : + string_has_parameter(value, "ce_delay") ? ANIM_CE_DELAY : + string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL : + string_has_parameter(value, "vertical") ? ANIM_VERTICAL : + 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 (value != NULL) - element_info[i].custom_description = getStringCopy(value); + if (string_has_parameter(value, "once")) + result |= ANIM_ONCE; - free(token); + if (string_has_parameter(value, "reverse")) + result |= ANIM_REVERSE; + + if (string_has_parameter(value, "opaque_player")) + result |= ANIM_OPAQUE_PLAYER; + + if (string_has_parameter(value, "static_panel")) + result |= ANIM_STATIC_PANEL; + } + else if (strEqual(suffix, ".init_event") || + strEqual(suffix, ".anim_event")) + { + result = get_anim_parameter_values(value); + } + else if (strEqual(suffix, ".init_delay_action") || + strEqual(suffix, ".anim_delay_action") || + strEqual(suffix, ".post_delay_action") || + strEqual(suffix, ".init_event_action") || + strEqual(suffix, ".anim_event_action")) + { + result = get_anim_action_parameter_value(value_raw); + } + else if (strEqual(suffix, ".class")) + { + result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE : + get_hash_from_string(value)); } + else if (strEqual(suffix, ".style")) + { + result = STYLE_DEFAULT; - freeSetupFileHash(setup_file_hash); -} + if (string_has_parameter(value, "accurate_borders")) + result |= STYLE_ACCURATE_BORDERS; -static int getElementFromToken(char *token) -{ - char *value = getHashEntry(element_token_hash, token); + if (string_has_parameter(value, "inner_corners")) + result |= STYLE_INNER_CORNERS; - if (value != NULL) - return atoi(value); + if (string_has_parameter(value, "reverse")) + result |= STYLE_REVERSE; - Error(ERR_WARN, "unknown element token '%s'", token); + if (string_has_parameter(value, "leftmost_position")) + result |= STYLE_LEFTMOST_POSITION; - return EL_UNDEFINED; + if (string_has_parameter(value, "block_clicks")) + result |= STYLE_BLOCK; + + if (string_has_parameter(value, "passthrough_clicks")) + result |= STYLE_PASSTHROUGH; + + 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 : + FADE_MODE_DEFAULT); + } + else if (strEqual(suffix, ".auto_delay_unit")) + { + result = (string_has_parameter(value, "ms") ? AUTO_DELAY_UNIT_MS : + string_has_parameter(value, "frames") ? AUTO_DELAY_UNIT_FRAMES : + AUTO_DELAY_UNIT_DEFAULT); + } + else if (strPrefix(suffix, ".font")) // (may also be ".font_xyz") + { + result = gfx.get_font_from_token_function(value); + } + else // generic parameter of type integer or boolean + { + result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE : + type == TYPE_INTEGER ? get_integer_from_string(value) : + type == TYPE_BOOLEAN ? get_boolean_from_string(value) : + ARG_UNDEFINED_VALUE); + } + + free(value); + + return result; } static int get_token_parameter_value(char *token, char *value_raw) @@ -9703,14 +13177,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 = @@ -9718,6 +13196,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; @@ -9734,10 +13218,14 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void) title_initial_first_default.post_delay; titlescreen_initial_first_default.auto_delay = title_initial_first_default.auto_delay; + titlescreen_initial_first_default.auto_delay_unit = + title_initial_first_default.auto_delay_unit; titlescreen_first_default.fade_mode = title_first_default.fade_mode; titlescreen_first_default.fade_delay = title_first_default.fade_delay; titlescreen_first_default.post_delay = title_first_default.post_delay; titlescreen_first_default.auto_delay = title_first_default.auto_delay; + titlescreen_first_default.auto_delay_unit = + title_first_default.auto_delay_unit; titlemessage_initial_first_default.fade_mode = title_initial_first_default.fade_mode; titlemessage_initial_first_default.fade_delay = @@ -9746,27 +13234,36 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void) title_initial_first_default.post_delay; titlemessage_initial_first_default.auto_delay = title_initial_first_default.auto_delay; + titlemessage_initial_first_default.auto_delay_unit = + title_initial_first_default.auto_delay_unit; titlemessage_first_default.fade_mode = title_first_default.fade_mode; titlemessage_first_default.fade_delay = title_first_default.fade_delay; titlemessage_first_default.post_delay = title_first_default.post_delay; titlemessage_first_default.auto_delay = title_first_default.auto_delay; + titlemessage_first_default.auto_delay_unit = + title_first_default.auto_delay_unit; titlescreen_initial_default.fade_mode = title_initial_default.fade_mode; titlescreen_initial_default.fade_delay = title_initial_default.fade_delay; titlescreen_initial_default.post_delay = title_initial_default.post_delay; titlescreen_initial_default.auto_delay = title_initial_default.auto_delay; + titlescreen_initial_default.auto_delay_unit = + title_initial_default.auto_delay_unit; titlescreen_default.fade_mode = title_default.fade_mode; titlescreen_default.fade_delay = title_default.fade_delay; titlescreen_default.post_delay = title_default.post_delay; titlescreen_default.auto_delay = title_default.auto_delay; + titlescreen_default.auto_delay_unit = title_default.auto_delay_unit; titlemessage_initial_default.fade_mode = title_initial_default.fade_mode; titlemessage_initial_default.fade_delay = title_initial_default.fade_delay; titlemessage_initial_default.post_delay = title_initial_default.post_delay; - titlemessage_initial_default.auto_delay = title_initial_default.auto_delay; + titlemessage_initial_default.auto_delay_unit = + title_initial_default.auto_delay_unit; titlemessage_default.fade_mode = title_default.fade_mode; titlemessage_default.fade_delay = title_default.fade_delay; titlemessage_default.post_delay = title_default.post_delay; titlemessage_default.auto_delay = title_default.auto_delay; + titlemessage_default.auto_delay_unit = title_default.auto_delay_unit; // special case: initialize "ARG_DEFAULT" values in static default config // (e.g., init "titlemessage_1.fade_mode" from "[titlemessage].fade_mode") @@ -9826,17 +13323,248 @@ static void InitMenuDesignSettings_SpecialPostProcessing(void) { NULL, NULL } }; - int i; + int i, j; + + // special case: initialize later added SETUP list size from LEVELS value + if (menu.list_size[GAME_MODE_SETUP] == -1) + menu.list_size[GAME_MODE_SETUP] = menu.list_size[GAME_MODE_LEVELS]; + + // set default position for snapshot buttons to stop/pause/play buttons + for (i = 0; game_buttons_xy[i].dst != NULL; i++) + if ((*game_buttons_xy[i].dst).x == -1 && + (*game_buttons_xy[i].dst).y == -1) + *game_buttons_xy[i].dst = *game_buttons_xy[i].src; + + // -------------------------------------------------------------------------- + // dynamic viewports (including playfield margins, borders and alignments) + // -------------------------------------------------------------------------- + + // dynamic viewports currently only supported for landscape mode + int display_width = MAX(video.display_width, video.display_height); + int display_height = MIN(video.display_width, video.display_height); + + for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++) + { + struct RectWithBorder *vp_window = &viewport.window[i]; + struct RectWithBorder *vp_playfield = &viewport.playfield[i]; + struct RectWithBorder *vp_door_1 = &viewport.door_1[i]; + struct RectWithBorder *vp_door_2 = &viewport.door_2[i]; + boolean dynamic_window_width = (vp_window->min_width != -1); + boolean dynamic_window_height = (vp_window->min_height != -1); + boolean dynamic_playfield_width = (vp_playfield->min_width != -1); + boolean dynamic_playfield_height = (vp_playfield->min_height != -1); + + // adjust window size if min/max width/height is specified + + if (vp_window->min_width != -1) + { + int window_width = display_width; + + // when using static window height, use aspect ratio of display + if (vp_window->min_height == -1) + window_width = vp_window->height * display_width / display_height; + + vp_window->width = MAX(vp_window->min_width, window_width); + } + + if (vp_window->min_height != -1) + { + int window_height = display_height; + + // when using static window width, use aspect ratio of display + if (vp_window->min_width == -1) + window_height = vp_window->width * display_height / display_width; + + vp_window->height = MAX(vp_window->min_height, window_height); + } + + if (vp_window->max_width != -1) + vp_window->width = MIN(vp_window->width, vp_window->max_width); + + if (vp_window->max_height != -1) + vp_window->height = MIN(vp_window->height, vp_window->max_height); + + int playfield_width = vp_window->width; + int playfield_height = vp_window->height; + + // adjust playfield size and position according to specified margins + + playfield_width -= vp_playfield->margin_left; + playfield_width -= vp_playfield->margin_right; + + playfield_height -= vp_playfield->margin_top; + playfield_height -= vp_playfield->margin_bottom; + + // adjust playfield size if min/max width/height is specified + + if (vp_playfield->min_width != -1) + vp_playfield->width = MAX(vp_playfield->min_width, playfield_width); + + if (vp_playfield->min_height != -1) + vp_playfield->height = MAX(vp_playfield->min_height, playfield_height); + + if (vp_playfield->max_width != -1) + 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); + + // adjust playfield position according to specified alignment + + if (vp_playfield->align == ALIGN_LEFT || vp_playfield->x > 0) + vp_playfield->x = ALIGNED_VP_XPOS(vp_playfield); + else if (vp_playfield->align == ALIGN_CENTER) + vp_playfield->x = playfield_width / 2 - vp_playfield->width / 2; + else if (vp_playfield->align == ALIGN_RIGHT) + vp_playfield->x += playfield_width - vp_playfield->width; + + if (vp_playfield->valign == VALIGN_TOP || vp_playfield->y > 0) + vp_playfield->y = ALIGNED_VP_YPOS(vp_playfield); + else if (vp_playfield->valign == VALIGN_MIDDLE) + vp_playfield->y = playfield_height / 2 - vp_playfield->height / 2; + else if (vp_playfield->valign == VALIGN_BOTTOM) + vp_playfield->y += playfield_height - vp_playfield->height; + + vp_playfield->x += vp_playfield->margin_left; + vp_playfield->y += vp_playfield->margin_top; + + // adjust individual playfield borders if only default border is specified + + if (vp_playfield->border_left == -1) + vp_playfield->border_left = vp_playfield->border_size; + if (vp_playfield->border_right == -1) + vp_playfield->border_right = vp_playfield->border_size; + if (vp_playfield->border_top == -1) + vp_playfield->border_top = vp_playfield->border_size; + if (vp_playfield->border_bottom == -1) + vp_playfield->border_bottom = vp_playfield->border_size; + + // set dynamic playfield borders if borders are specified as undefined + // (but only if window size was dynamic and playfield size was static) + + if (dynamic_window_width && !dynamic_playfield_width) + { + if (vp_playfield->border_left == -1) + { + vp_playfield->border_left = (vp_playfield->x - + vp_playfield->margin_left); + vp_playfield->x -= vp_playfield->border_left; + vp_playfield->width += vp_playfield->border_left; + } + + if (vp_playfield->border_right == -1) + { + vp_playfield->border_right = (vp_window->width - + vp_playfield->x - + vp_playfield->width - + vp_playfield->margin_right); + vp_playfield->width += vp_playfield->border_right; + } + } + + if (dynamic_window_height && !dynamic_playfield_height) + { + if (vp_playfield->border_top == -1) + { + vp_playfield->border_top = (vp_playfield->y - + vp_playfield->margin_top); + vp_playfield->y -= vp_playfield->border_top; + vp_playfield->height += vp_playfield->border_top; + } + + if (vp_playfield->border_bottom == -1) + { + vp_playfield->border_bottom = (vp_window->height - + vp_playfield->y - + vp_playfield->height - + vp_playfield->margin_bottom); + vp_playfield->height += vp_playfield->border_bottom; + } + } + + // adjust playfield size to be a multiple of a defined alignment tile size + + int align_size = vp_playfield->align_size; + int playfield_xtiles = vp_playfield->width / align_size; + int playfield_ytiles = vp_playfield->height / align_size; + int playfield_width_corrected = playfield_xtiles * align_size; + int playfield_height_corrected = playfield_ytiles * align_size; + boolean is_playfield_mode = (i == GFX_SPECIAL_ARG_PLAYING || + i == GFX_SPECIAL_ARG_EDITOR); + + if (is_playfield_mode && + dynamic_playfield_width && + vp_playfield->width != playfield_width_corrected) + { + int playfield_xdiff = vp_playfield->width - playfield_width_corrected; + + vp_playfield->width = playfield_width_corrected; + + if (vp_playfield->align == ALIGN_LEFT) + { + vp_playfield->border_left += playfield_xdiff; + } + else if (vp_playfield->align == ALIGN_RIGHT) + { + vp_playfield->border_right += playfield_xdiff; + } + else if (vp_playfield->align == ALIGN_CENTER) + { + int border_left_diff = playfield_xdiff / 2; + int border_right_diff = playfield_xdiff - border_left_diff; + + vp_playfield->border_left += border_left_diff; + vp_playfield->border_right += border_right_diff; + } + } + + if (is_playfield_mode && + dynamic_playfield_height && + vp_playfield->height != playfield_height_corrected) + { + int playfield_ydiff = vp_playfield->height - playfield_height_corrected; + + vp_playfield->height = playfield_height_corrected; + + if (vp_playfield->valign == VALIGN_TOP) + { + vp_playfield->border_top += playfield_ydiff; + } + else if (vp_playfield->align == VALIGN_BOTTOM) + { + vp_playfield->border_right += playfield_ydiff; + } + else if (vp_playfield->align == VALIGN_MIDDLE) + { + int border_top_diff = playfield_ydiff / 2; + int border_bottom_diff = playfield_ydiff - border_top_diff; + + vp_playfield->border_top += border_top_diff; + vp_playfield->border_bottom += border_bottom_diff; + } + } - // special case: initialize later added SETUP list size from LEVELS value - if (menu.list_size[GAME_MODE_SETUP] == -1) - menu.list_size[GAME_MODE_SETUP] = menu.list_size[GAME_MODE_LEVELS]; + // adjust door positions according to specified alignment - // set default position for snapshot buttons to stop/pause/play buttons - for (i = 0; game_buttons_xy[i].dst != NULL; i++) - if ((*game_buttons_xy[i].dst).x == -1 && - (*game_buttons_xy[i].dst).y == -1) - *game_buttons_xy[i].dst = *game_buttons_xy[i].src; + for (j = 0; j < 2; j++) + { + struct RectWithBorder *vp_door = (j == 0 ? vp_door_1 : vp_door_2); + + if (vp_door->align == ALIGN_LEFT || vp_door->x > 0) + vp_door->x = ALIGNED_VP_XPOS(vp_door); + else if (vp_door->align == ALIGN_CENTER) + vp_door->x = vp_window->width / 2 - vp_door->width / 2; + else if (vp_door->align == ALIGN_RIGHT) + vp_door->x += vp_window->width - vp_door->width; + + if (vp_door->valign == VALIGN_TOP || vp_door->y > 0) + vp_door->y = ALIGNED_VP_YPOS(vp_door); + else if (vp_door->valign == VALIGN_MIDDLE) + vp_door->y = vp_window->height / 2 - vp_door->height / 2; + else if (vp_door->valign == VALIGN_BOTTOM) + vp_door->y += vp_window->height - vp_door->height; + } + } } static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void) @@ -9878,6 +13606,79 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void) *editor_buttons_xy[i].dst = *editor_buttons_xy[i].src; } } + + // adjust editor palette rows and columns if specified to be dynamic + + if (editor.palette.cols == -1) + { + int vp_width = viewport.playfield[GFX_SPECIAL_ARG_EDITOR].width; + int bt_width = graphic_info[IMG_EDITOR_PALETTE_BUTTON].width; + int sc_width = graphic_info[IMG_EDITOR_PALETTE_SCROLLBAR].width; + + editor.palette.cols = (vp_width - sc_width) / bt_width; + + if (editor.palette.x == -1) + { + int palette_width = editor.palette.cols * bt_width + sc_width; + + editor.palette.x = (vp_width - palette_width) / 2; + } + } + + if (editor.palette.rows == -1) + { + int vp_height = viewport.playfield[GFX_SPECIAL_ARG_EDITOR].height; + int bt_height = graphic_info[IMG_EDITOR_PALETTE_BUTTON].height; + int tx_height = getFontHeight(FONT_TEXT_2); + + editor.palette.rows = (vp_height - tx_height) / bt_height; + + if (editor.palette.y == -1) + { + int palette_height = editor.palette.rows * bt_height + tx_height; + + editor.palette.y = (vp_height - palette_height) / 2; + } + } +} + +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) @@ -9890,6 +13691,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { TYPE_INTEGER, &tfi.fade_delay, ".fade_delay" }, { TYPE_INTEGER, &tfi.post_delay, ".post_delay" }, { TYPE_INTEGER, &tfi.auto_delay, ".auto_delay" }, + { TYPE_INTEGER, &tfi.auto_delay_unit, ".auto_delay_unit" }, { -1, NULL, NULL } }; @@ -9912,6 +13714,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { TYPE_INTEGER, &tmi.fade_delay, ".fade_delay" }, { TYPE_INTEGER, &tmi.post_delay, ".post_delay" }, { TYPE_INTEGER, &tmi.auto_delay, ".auto_delay" }, + { TYPE_INTEGER, &tmi.auto_delay_unit, ".auto_delay_unit" }, { -1, NULL, NULL } }; @@ -9987,36 +13790,43 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) // (e.g., init "menu.draw_xoffset.INFO" from "menu.draw_xoffset") for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++) { - char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset"); - char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset"); - char *value_3 = getHashEntry(setup_file_hash, "menu.list_size"); + struct TokenIntPtrInfo menu_config[] = + { + { "menu.draw_xoffset", &menu.draw_xoffset[i] }, + { "menu.draw_yoffset", &menu.draw_yoffset[i] }, + { "menu.list_size", &menu.list_size[i] } + }; + + for (j = 0; j < ARRAY_SIZE(menu_config); j++) + { + char *token = menu_config[j].token; + char *value = getHashEntry(setup_file_hash, token); - if (value_1 != NULL) - menu.draw_xoffset[i] = get_integer_from_string(value_1); - if (value_2 != NULL) - menu.draw_yoffset[i] = get_integer_from_string(value_2); - if (value_3 != NULL) - menu.list_size[i] = get_integer_from_string(value_3); + if (value != NULL) + *menu_config[j].value = get_integer_from_string(value); + } } // special case: initialize with default values that may be overwritten // (eg, init "menu.draw_xoffset.INFO[XXX]" from "menu.draw_xoffset.INFO") for (i = 0; i < NUM_SPECIAL_GFX_INFO_ARGS; i++) { - char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset.INFO"); - char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset.INFO"); - - if (value_1 != NULL) - menu.draw_xoffset_info[i] = get_integer_from_string(value_1); - if (value_2 != NULL) - menu.draw_yoffset_info[i] = get_integer_from_string(value_2); + struct TokenIntPtrInfo menu_config[] = + { + { "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_entry_size.INFO", &menu.list_entry_size_info[i] }, + { "menu.tile_size.INFO", &menu.tile_size_info[i] } + }; - if (i == GFX_SPECIAL_ARG_INFO_ELEMENTS) + for (j = 0; j < ARRAY_SIZE(menu_config); j++) { - char *value_1 = getHashEntry(setup_file_hash, "menu.list_size.INFO"); + char *token = menu_config[j].token; + char *value = getHashEntry(setup_file_hash, token); - if (value_1 != NULL) - menu.list_size_info[i] = get_integer_from_string(value_1); + if (value != NULL) + *menu_config[j].value = get_integer_from_string(value); } } @@ -10024,179 +13834,132 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) // (eg, init "menu.draw_xoffset.SETUP[XXX]" from "menu.draw_xoffset.SETUP") for (i = 0; i < NUM_SPECIAL_GFX_SETUP_ARGS; i++) { - char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset.SETUP"); - char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset.SETUP"); + struct TokenIntPtrInfo menu_config[] = + { + { "menu.draw_xoffset.SETUP", &menu.draw_xoffset_setup[i] }, + { "menu.draw_yoffset.SETUP", &menu.draw_yoffset_setup[i] } + }; + + for (j = 0; j < ARRAY_SIZE(menu_config); j++) + { + char *token = menu_config[j].token; + char *value = getHashEntry(setup_file_hash, token); - if (value_1 != NULL) - menu.draw_xoffset_setup[i] = get_integer_from_string(value_1); - if (value_2 != NULL) - menu.draw_yoffset_setup[i] = get_integer_from_string(value_2); + if (value != NULL) + *menu_config[j].value = get_integer_from_string(value); + } } // special case: initialize with default values that may be overwritten // (eg, init "menu.line_spacing.INFO[XXX]" from "menu.line_spacing.INFO") for (i = 0; i < NUM_SPECIAL_GFX_INFO_ARGS; i++) { - char *value_1 = getHashEntry(setup_file_hash,"menu.left_spacing.INFO"); - char *value_2 = getHashEntry(setup_file_hash,"menu.right_spacing.INFO"); - char *value_3 = getHashEntry(setup_file_hash,"menu.top_spacing.INFO"); - char *value_4 = getHashEntry(setup_file_hash,"menu.bottom_spacing.INFO"); - char *value_5 = getHashEntry(setup_file_hash,"menu.paragraph_spacing.INFO"); - char *value_6 = getHashEntry(setup_file_hash,"menu.headline1_spacing.INFO"); - char *value_7 = getHashEntry(setup_file_hash,"menu.headline2_spacing.INFO"); - char *value_8 = getHashEntry(setup_file_hash,"menu.line_spacing.INFO"); - char *value_9 = getHashEntry(setup_file_hash,"menu.extra_spacing.INFO"); - - if (value_1 != NULL) - menu.left_spacing_info[i] = get_integer_from_string(value_1); - if (value_2 != NULL) - menu.right_spacing_info[i] = get_integer_from_string(value_2); - if (value_3 != NULL) - menu.top_spacing_info[i] = get_integer_from_string(value_3); - if (value_4 != NULL) - menu.bottom_spacing_info[i] = get_integer_from_string(value_4); - if (value_5 != NULL) - menu.paragraph_spacing_info[i] = get_integer_from_string(value_5); - if (value_6 != NULL) - menu.headline1_spacing_info[i] = get_integer_from_string(value_6); - if (value_7 != NULL) - menu.headline2_spacing_info[i] = get_integer_from_string(value_7); - if (value_8 != NULL) - menu.line_spacing_info[i] = get_integer_from_string(value_8); - if (value_9 != NULL) - menu.extra_spacing_info[i] = get_integer_from_string(value_9); + 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] }, + { "menu.paragraph_spacing.INFO", &menu.paragraph_spacing_info[i] }, + { "menu.headline1_spacing.INFO", &menu.headline1_spacing_info[i] }, + { "menu.headline2_spacing.INFO", &menu.headline2_spacing_info[i] }, + { "menu.line_spacing.INFO", &menu.line_spacing_info[i] }, + { "menu.extra_spacing.INFO", &menu.extra_spacing_info[i] }, + }; + + for (j = 0; j < ARRAY_SIZE(menu_config); j++) + { + char *token = menu_config[j].token; + char *value = getHashEntry(setup_file_hash, token); + + if (value != NULL) + *menu_config[j].value = get_integer_from_string(value); + } } // special case: initialize with default values that may be overwritten // (eg, init "menu.enter_screen.SCORES.xyz" from "menu.enter_screen.xyz") for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++) { - char *token_1 = "menu.enter_screen.fade_mode"; - char *token_2 = "menu.enter_screen.fade_delay"; - char *token_3 = "menu.enter_screen.post_delay"; - char *token_4 = "menu.leave_screen.fade_mode"; - char *token_5 = "menu.leave_screen.fade_delay"; - char *token_6 = "menu.leave_screen.post_delay"; - char *token_7 = "menu.next_screen.fade_mode"; - char *token_8 = "menu.next_screen.fade_delay"; - char *token_9 = "menu.next_screen.post_delay"; - char *value_1 = getHashEntry(setup_file_hash, token_1); - char *value_2 = getHashEntry(setup_file_hash, token_2); - char *value_3 = getHashEntry(setup_file_hash, token_3); - char *value_4 = getHashEntry(setup_file_hash, token_4); - char *value_5 = getHashEntry(setup_file_hash, token_5); - char *value_6 = getHashEntry(setup_file_hash, token_6); - char *value_7 = getHashEntry(setup_file_hash, token_7); - char *value_8 = getHashEntry(setup_file_hash, token_8); - char *value_9 = getHashEntry(setup_file_hash, token_9); - - if (value_1 != NULL) - menu.enter_screen[i].fade_mode = get_token_parameter_value(token_1, - value_1); - if (value_2 != NULL) - menu.enter_screen[i].fade_delay = get_token_parameter_value(token_2, - value_2); - if (value_3 != NULL) - menu.enter_screen[i].post_delay = get_token_parameter_value(token_3, - value_3); - if (value_4 != NULL) - menu.leave_screen[i].fade_mode = get_token_parameter_value(token_4, - value_4); - if (value_5 != NULL) - menu.leave_screen[i].fade_delay = get_token_parameter_value(token_5, - value_5); - if (value_6 != NULL) - menu.leave_screen[i].post_delay = get_token_parameter_value(token_6, - value_6); - if (value_7 != NULL) - menu.next_screen[i].fade_mode = get_token_parameter_value(token_7, - value_7); - if (value_8 != NULL) - menu.next_screen[i].fade_delay = get_token_parameter_value(token_8, - value_8); - if (value_9 != NULL) - menu.next_screen[i].post_delay = get_token_parameter_value(token_9, - value_9); + struct TokenIntPtrInfo menu_config[] = + { + { "menu.enter_screen.fade_mode", &menu.enter_screen[i].fade_mode }, + { "menu.enter_screen.fade_delay", &menu.enter_screen[i].fade_delay }, + { "menu.enter_screen.post_delay", &menu.enter_screen[i].post_delay }, + { "menu.leave_screen.fade_mode", &menu.leave_screen[i].fade_mode }, + { "menu.leave_screen.fade_delay", &menu.leave_screen[i].fade_delay }, + { "menu.leave_screen.post_delay", &menu.leave_screen[i].post_delay }, + { "menu.next_screen.fade_mode", &menu.next_screen[i].fade_mode }, + { "menu.next_screen.fade_delay", &menu.next_screen[i].fade_delay }, + { "menu.next_screen.post_delay", &menu.next_screen[i].post_delay } + }; + + for (j = 0; j < ARRAY_SIZE(menu_config); j++) + { + char *token = menu_config[j].token; + char *value = getHashEntry(setup_file_hash, token); + + if (value != NULL) + *menu_config[j].value = get_token_parameter_value(token, value); + } } // special case: initialize with default values that may be overwritten // (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz") for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++) { - char *token_w1 = "viewport.window.width"; - char *token_w2 = "viewport.window.height"; - char *token_01 = "viewport.playfield.x"; - char *token_02 = "viewport.playfield.y"; - char *token_03 = "viewport.playfield.width"; - char *token_04 = "viewport.playfield.height"; - char *token_05 = "viewport.playfield.border_size"; - char *token_06 = "viewport.door_1.x"; - char *token_07 = "viewport.door_1.y"; - char *token_08 = "viewport.door_1.width"; - char *token_09 = "viewport.door_1.height"; - char *token_10 = "viewport.door_1.border_size"; - char *token_11 = "viewport.door_2.x"; - char *token_12 = "viewport.door_2.y"; - char *token_13 = "viewport.door_2.width"; - char *token_14 = "viewport.door_2.height"; - char *token_15 = "viewport.door_2.border_size"; - char *value_w1 = getHashEntry(setup_file_hash, token_w1); - char *value_w2 = getHashEntry(setup_file_hash, token_w2); - char *value_01 = getHashEntry(setup_file_hash, token_01); - char *value_02 = getHashEntry(setup_file_hash, token_02); - char *value_03 = getHashEntry(setup_file_hash, token_03); - char *value_04 = getHashEntry(setup_file_hash, token_04); - char *value_05 = getHashEntry(setup_file_hash, token_05); - char *value_06 = getHashEntry(setup_file_hash, token_06); - char *value_07 = getHashEntry(setup_file_hash, token_07); - char *value_08 = getHashEntry(setup_file_hash, token_08); - char *value_09 = getHashEntry(setup_file_hash, token_09); - char *value_10 = getHashEntry(setup_file_hash, token_10); - char *value_11 = getHashEntry(setup_file_hash, token_11); - char *value_12 = getHashEntry(setup_file_hash, token_12); - char *value_13 = getHashEntry(setup_file_hash, token_13); - char *value_14 = getHashEntry(setup_file_hash, token_14); - char *value_15 = getHashEntry(setup_file_hash, token_15); - - if (value_w1 != NULL) - viewport.window[i].width = get_token_parameter_value(token_w1, value_w1); - if (value_w2 != NULL) - viewport.window[i].height = get_token_parameter_value(token_w2, value_w2); - if (value_01 != NULL) - viewport.playfield[i].x = get_token_parameter_value(token_01, value_01); - if (value_02 != NULL) - viewport.playfield[i].y = get_token_parameter_value(token_02, value_02); - if (value_03 != NULL) - viewport.playfield[i].width = get_token_parameter_value(token_03, - value_03); - if (value_04 != NULL) - viewport.playfield[i].height = get_token_parameter_value(token_04, - value_04); - if (value_05 != NULL) - viewport.playfield[i].border_size = get_token_parameter_value(token_05, - value_05); - if (value_06 != NULL) - viewport.door_1[i].x = get_token_parameter_value(token_06, value_06); - if (value_07 != NULL) - viewport.door_1[i].y = get_token_parameter_value(token_07, value_07); - if (value_08 != NULL) - viewport.door_1[i].width = get_token_parameter_value(token_08, value_08); - if (value_09 != NULL) - viewport.door_1[i].height = get_token_parameter_value(token_09, value_09); - if (value_10 != NULL) - viewport.door_1[i].border_size = get_token_parameter_value(token_10, - value_10); - if (value_11 != NULL) - viewport.door_2[i].x = get_token_parameter_value(token_11, value_11); - if (value_12 != NULL) - viewport.door_2[i].y = get_token_parameter_value(token_12, value_12); - if (value_13 != NULL) - viewport.door_2[i].width = get_token_parameter_value(token_13, value_13); - if (value_14 != NULL) - viewport.door_2[i].height = get_token_parameter_value(token_14, value_14); - if (value_15 != NULL) - viewport.door_1[i].border_size = get_token_parameter_value(token_15, - value_15); + struct + { + char *token_prefix; + struct RectWithBorder *struct_ptr; + } + vp_struct[] = + { + { "viewport.window", &viewport.window[i] }, + { "viewport.playfield", &viewport.playfield[i] }, + { "viewport.door_1", &viewport.door_1[i] }, + { "viewport.door_2", &viewport.door_2[i] } + }; + + for (j = 0; j < ARRAY_SIZE(vp_struct); j++) + { + struct TokenIntPtrInfo vp_config[] = + { + { ".x", &vp_struct[j].struct_ptr->x }, + { ".y", &vp_struct[j].struct_ptr->y }, + { ".width", &vp_struct[j].struct_ptr->width }, + { ".height", &vp_struct[j].struct_ptr->height }, + { ".min_width", &vp_struct[j].struct_ptr->min_width }, + { ".min_height", &vp_struct[j].struct_ptr->min_height }, + { ".max_width", &vp_struct[j].struct_ptr->max_width }, + { ".max_height", &vp_struct[j].struct_ptr->max_height }, + { ".margin_left", &vp_struct[j].struct_ptr->margin_left }, + { ".margin_right", &vp_struct[j].struct_ptr->margin_right }, + { ".margin_top", &vp_struct[j].struct_ptr->margin_top }, + { ".margin_bottom", &vp_struct[j].struct_ptr->margin_bottom }, + { ".border_left", &vp_struct[j].struct_ptr->border_left }, + { ".border_right", &vp_struct[j].struct_ptr->border_right }, + { ".border_top", &vp_struct[j].struct_ptr->border_top }, + { ".border_bottom", &vp_struct[j].struct_ptr->border_bottom }, + { ".border_size", &vp_struct[j].struct_ptr->border_size }, + { ".align_size", &vp_struct[j].struct_ptr->align_size }, + { ".align", &vp_struct[j].struct_ptr->align }, + { ".valign", &vp_struct[j].struct_ptr->valign } + }; + + for (k = 0; k < ARRAY_SIZE(vp_config); k++) + { + char *token = getStringCat2(vp_struct[j].token_prefix, + vp_config[k].token); + char *value = getHashEntry(setup_file_hash, token); + + if (value != NULL) + *vp_config[k].value = get_token_parameter_value(token, value); + + free(token); + } + } } // special case: initialize with default values that may be overwritten @@ -10260,15 +14023,10 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) } // 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); } @@ -10279,6 +14037,7 @@ void LoadMenuDesignSettings(void) InitMenuDesignSettings_Static(); InitMenuDesignSettings_SpecialPreProcessing(); + InitMenuDesignSettings_PreviewPlayers(); if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS)) { @@ -10302,6 +14061,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(); @@ -10357,19 +14175,19 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements) { if (num_unknown_tokens == 0) { - Error(ERR_INFO_LINE, "-"); - Error(ERR_INFO, "warning: unknown token(s) found in config file:"); - Error(ERR_INFO, "- config file: '%s'", filename); + Warn("---"); + Warn("unknown token(s) found in config file:"); + Warn("- config file: '%s'", filename); num_unknown_tokens++; } - Error(ERR_INFO, "- token: '%s'", list->token); + Warn("- token: '%s'", list->token); } } if (num_unknown_tokens > 0) - Error(ERR_INFO_LINE, "-"); + Warn("---"); while (*num_elements % 4) // pad with empty elements, if needed (*elements)[(*num_elements)++] = EL_EMPTY; @@ -10379,8 +14197,8 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements) #if 0 for (i = 0; i < *num_elements; i++) - printf("editor: element '%s' [%d]\n", - element_info[(*elements)[i]].token_name, (*elements)[i]); + Debug("editor", "element '%s' [%d]\n", + element_info[(*elements)[i]].token_name, (*elements)[i]); #endif } @@ -10401,11 +14219,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 }, }; @@ -10501,14 +14321,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) @@ -10521,11 +14339,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); @@ -10534,75 +14354,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) - { - Error(ERR_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); @@ -10624,6 +14437,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, @@ -10647,18 +14472,18 @@ static void print_unknown_token(char *filename, char *token, int token_nr) { if (token_nr == 0) { - Error(ERR_INFO_LINE, "-"); - Error(ERR_INFO, "warning: unknown token(s) found in config file:"); - Error(ERR_INFO, "- config file: '%s'", filename); + Warn("---"); + Warn("unknown token(s) found in config file:"); + Warn("- config file: '%s'", filename); } - Error(ERR_INFO, "- token: '%s'", token); + Warn("- token: '%s'", token); } static void print_unknown_token_end(int token_nr) { if (token_nr > 0) - Error(ERR_INFO_LINE, "-"); + Warn("---"); } void LoadHelpAnimInfo(void) @@ -10862,12 +14687,12 @@ void LoadHelpAnimInfo(void) #if 0 for (i = 0; i < num_list_entries; i++) - printf("::: '%s': %d, %d, %d => %d\n", - EL_NAME(helpanim_info[i].element), - helpanim_info[i].element, - helpanim_info[i].action, - helpanim_info[i].direction, - helpanim_info[i].delay); + Debug("files:LoadHelpAnimInfo", "'%s': %d, %d, %d => %d", + EL_NAME(helpanim_info[i].element), + helpanim_info[i].element, + helpanim_info[i].action, + helpanim_info[i].direction, + helpanim_info[i].delay); #endif } @@ -10899,8 +14724,8 @@ void LoadHelpTextInfo(void) #if 0 BEGIN_HASH_ITERATION(helptext_info, itr) { - printf("::: '%s' => '%s'\n", - HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr)); + Debug("files:LoadHelpTextInfo", "'%s' => '%s'", + HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr)); } END_HASH_ITERATION(hash, itr) #endif @@ -10926,8 +14751,7 @@ void ConvertLevels(void) global.convert_leveldir); if (convert_leveldir == NULL) - Error(ERR_EXIT, "no such level identifier: '%s'", - global.convert_leveldir); + Fail("no such level identifier: '%s'", global.convert_leveldir); leveldir_current = convert_leveldir; @@ -10970,6 +14794,11 @@ void ConvertLevels(void) Print("converting level ... "); +#if 0 + // special case: conversion of some EMC levels as requested by ACME + level.game_engine_type = GAME_ENGINE_TYPE_RND; +#endif + level_filename = getDefaultLevelFilename(level_nr); new_level = !fileExists(level_filename); @@ -11027,7 +14856,6 @@ void ConvertLevels(void) void CreateLevelSketchImages(void) { -#if defined(TARGET_SDL) Bitmap *bitmap1; Bitmap *bitmap2; int i; @@ -11039,51 +14867,186 @@ void CreateLevelSketchImages(void) for (i = 0; i < NUM_FILE_ELEMENTS; i++) { - Bitmap *src_bitmap; - int src_x, src_y; int element = getMappedElement(i); - int graphic = el2edimg(element); char basename1[16]; char basename2[16]; char *filename1; char *filename2; - sprintf(basename1, "%03d.bmp", i); - sprintf(basename2, "%03ds.bmp", i); + sprintf(basename1, "%04d.bmp", i); + sprintf(basename2, "%04ds.bmp", i); - filename1 = getPath2(global.create_images_dir, basename1); - filename2 = getPath2(global.create_images_dir, basename2); + filename1 = getPath2(global.create_sketch_images_dir, basename1); + filename2 = getPath2(global.create_sketch_images_dir, basename2); - getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y); - BlitBitmap(src_bitmap, bitmap1, src_x, src_y, TILEX, TILEY, - 0, 0); + DrawSizedElement(0, 0, element, TILESIZE); + BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0); if (SDL_SaveBMP(bitmap1->surface, filename1) != 0) - Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename1); + Fail("cannot save level sketch image file '%s'", filename1); - getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y); - BlitBitmap(src_bitmap, bitmap2, src_x, src_y, MINI_TILEX, MINI_TILEY, 0, 0); + DrawSizedElement(0, 0, element, MINI_TILESIZE); + BlitBitmap(drawto, bitmap2, SX, SY, MINI_TILEX, MINI_TILEY, 0, 0); if (SDL_SaveBMP(bitmap2->surface, filename2) != 0) - Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename2); + Fail("cannot save level sketch image file '%s'", filename2); free(filename1); free(filename2); + // create corresponding SQL statements (for normal and small images) + if (i < 1000) + { + printf("insert into phpbb_words values (NULL, '`%03d', '');\n", i, i); + printf("insert into phpbb_words values (NULL, '¸%03d', '');\n", i, i); + } + + printf("insert into phpbb_words values (NULL, '`%04d', '');\n", i, i); + printf("insert into phpbb_words values (NULL, '¸%04d', '');\n", i, i); + + // optional: create content for forum level sketch demonstration post if (options.debug) - printf("%03d `%03d%c", i, i, (i % 10 < 9 ? ' ' : '\n')); + fprintf(stderr, "%03d `%03d%c", i, i, (i % 10 < 9 ? ' ' : '\n')); } FreeBitmap(bitmap1); FreeBitmap(bitmap2); if (options.debug) - printf("\n"); + fprintf(stderr, "\n"); - Error(ERR_INFO, "%d normal and small images created", NUM_FILE_ELEMENTS); + Info("%d normal and small images created", NUM_FILE_ELEMENTS); + + CloseAllAndExit(0); +} + + +// ---------------------------------------------------------------------------- +// create and save images for element collecting animations (raw BMP format) +// ---------------------------------------------------------------------------- + +static boolean createCollectImage(int element) +{ + return (IS_COLLECTIBLE(element) && !IS_SP_ELEMENT(element)); +} + +void CreateCollectElementImages(void) +{ + int i, j; + int num_steps = 8; + int anim_frames = num_steps - 1; + int tile_size = TILESIZE; + int anim_width = tile_size * anim_frames; + int anim_height = tile_size; + int num_collect_images = 0; + int pos_collect_images = 0; + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + if (createCollectImage(i)) + num_collect_images++; + + Info("Creating %d element collecting animation images ...", + num_collect_images); + + 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_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++) + { + if (!createCollectImage(i)) + continue; + + int dst_x = (pos_collect_images / (num_collect_images / 2)) * anim_width; + int dst_y = (pos_collect_images % (num_collect_images / 2)) * anim_height; + int graphic = el2img(i); + char *token_name = element_info[i].token_name; + Bitmap *tmp_bitmap = CreateBitmap(tile_size, tile_size, DEFAULT_DEPTH); + Bitmap *src_bitmap; + int src_x, src_y; + + Info("- creating collecting image for '%s' ...", token_name); + + getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y); + + 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++) + { + int frame_size_final = tile_size * (anim_frames - j) / num_steps; + int frame_size = frame_size_final * num_steps; + int offset = (tile_size - frame_size_final) / 2; + Bitmap *frame_bitmap = ZoomBitmap(tmp_bitmap, frame_size, frame_size); + + while (frame_size > frame_size_final) + { + frame_size /= 2; + + Bitmap *half_bitmap = ZoomBitmap(frame_bitmap, frame_size, frame_size); + + FreeBitmap(frame_bitmap); + + frame_bitmap = half_bitmap; + } + + 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); + } + + tmp_bitmap->surface_masked = NULL; + + FreeBitmap(tmp_bitmap); + + pos_collect_images++; + } + + 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); -#endif } @@ -11093,7 +15056,6 @@ void CreateLevelSketchImages(void) void CreateCustomElementImages(char *directory) { -#if defined(TARGET_SDL) char *src_basename = "RocksCE-template.ilbm"; char *dst_basename = "RocksCE.bmp"; char *src_filename = getPath2(directory, src_basename); @@ -11104,7 +15066,7 @@ void CreateCustomElementImages(char *directory) int yoffset_ge = (TILEY * NUM_CUSTOM_ELEMENTS / 16); int i; - SDLInitVideoDisplay(); + InitVideoDefaults(); ReCreateBitmap(&backbuffer, video.width, video.height); @@ -11180,10 +15142,9 @@ void CreateCustomElementImages(char *directory) } if (SDL_SaveBMP(bitmap->surface, dst_filename) != 0) - Error(ERR_EXIT, "cannot save CE graphics file '%s'", dst_filename); + Fail("cannot save CE graphics file '%s'", dst_filename); FreeBitmap(bitmap); CloseAllAndExit(0); -#endif }