X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=b0bb49ecdbe2ac9de65ea04b82e44ae29535a932;hb=refs%2Fheads%2Fmaster-next-major-release;hp=442a0b674b915681014f63d50075ac4cf2928c50;hpb=de5791c86b3c80e18b1871d39b9ec207c96c7cbf;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index 442a0b67..c620a531 100644 --- a/src/files.c +++ b/src/files.c @@ -19,9 +19,11 @@ #include "files.h" #include "init.h" #include "screens.h" +#include "editor.h" #include "tools.h" #include "tape.h" #include "config.h" +#include "api.h" #define ENABLE_UNUSED_CODE 0 // currently unused functions #define ENABLE_HISTORIC_CHUNKS 0 // only for historic reference @@ -51,6 +53,7 @@ // (element number only) #define LEVEL_CHUNK_GRPX_UNCHANGED 2 +#define LEVEL_CHUNK_EMPX_UNCHANGED 2 #define LEVEL_CHUNK_NOTE_UNCHANGED 2 // (nothing at all if unchanged) @@ -58,7 +61,6 @@ #define TAPE_CHUNK_VERS_SIZE 8 // size of file version chunk #define TAPE_CHUNK_HEAD_SIZE 20 // size of tape file header -#define TAPE_CHUNK_HEAD_UNUSED 1 // unused tape header bytes #define TAPE_CHUNK_SCRN_SIZE 2 // size of screen size chunk #define SCORE_CHUNK_VERS_SIZE 8 // size of file version chunk @@ -113,7 +115,7 @@ CONF_CONTENT_NUM_BYTES : 1) #define CONF_ELEMENT_BYTE_POS(i) ((i) * CONF_ELEMENT_NUM_BYTES) -#define CONF_ELEMENTS_ELEMENT(b,i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ +#define CONF_ELEMENTS_ELEMENT(b, i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ (b[CONF_ELEMENT_BYTE_POS(i) + 1])) #define CONF_CONTENT_ELEMENT_POS(c,x,y) ((c) * CONF_CONTENT_NUM_ELEMENTS + \ @@ -164,7 +166,6 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = TYPE_INTEGER, CONF_VALUE_8_BIT(1), &li.game_engine_type, GAME_ENGINE_TYPE_RND }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(1), @@ -175,102 +176,176 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = TYPE_INTEGER, CONF_VALUE_16_BIT(2), &li.fieldy, STD_LEV_FIELDY }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(3), &li.time, 100 }, - { -1, SAVE_CONF_ALWAYS, TYPE_INTEGER, CONF_VALUE_16_BIT(4), &li.gems_needed, 0 }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_32_BIT(2), &li.random_seed, 0 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), &li.use_step_counter, FALSE }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_8_BIT(4), &li.wind_direction_initial, MV_NONE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), &li.em_slippery_gems, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), &li.use_custom_template, FALSE }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_32_BIT(1), &li.can_move_into_acid_bits, ~0 // default: everything can }, - { -1, -1, TYPE_BITFIELD, CONF_VALUE_8_BIT(7), &li.dont_collide_with_bits, ~0 // default: always deadly }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(8), &li.em_explodes_by_fire, FALSE }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(5), &li.score[SC_TIME_BONUS], 1 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), &li.auto_exit_sokoban, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(10), &li.auto_count_gems, FALSE }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(11), &li.solved_by_one_player, FALSE }, - { -1, -1, TYPE_INTEGER, CONF_VALUE_8_BIT(12), &li.time_score_base, 1 }, - { -1, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(13), &li.rate_time_over_score, FALSE }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(14), + &li.bd_intermission, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(15), + &li.bd_scheduling_type, GD_SCHEDULING_MILLISECONDS + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), + &li.bd_pal_timing, FALSE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(6), + &li.bd_cycle_delay_ms, 200 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(17), + &li.bd_cycle_delay_c64, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(18), + &li.bd_hatching_delay_cycles, 21 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(19), + &li.bd_hatching_delay_seconds, 2 + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(20), + &li.bd_line_shifting_borders, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(21), + &li.bd_scan_first_and_last_row, TRUE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(22), + &li.bd_short_explosions, TRUE + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(23), + &li.bd_cave_random_seed_c64, 0 + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(3), + &li.bd_color_b, GD_C64_COLOR(0) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(4), + &li.bd_color_0, GD_C64_COLOR(0) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(5), + &li.bd_color_1, GD_C64_COLOR(8) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(6), + &li.bd_color_2, GD_C64_COLOR(11) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(7), + &li.bd_color_3, GD_C64_COLOR(1) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(8), + &li.bd_color_4, GD_C64_COLOR(5) + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(9), + &li.bd_color_5, GD_C64_COLOR(6) + }, { -1, -1, @@ -562,771 +637,1293 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = &li.initial_inventory_size[3], 1, MAX_INITIAL_INVENTORY_SIZE }, + // (these values are only valid for BD style levels) + // (some values for BD style amoeba following below) { - EL_EMERALD, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_EMERALD], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_diagonal_movements, FALSE }, - { - EL_DIAMOND, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_DIAMOND], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_topmost_player_active, TRUE }, - { - EL_BUG, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_BUG], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_pushing_prob, 25 }, - { - EL_SPACESHIP, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_SPACESHIP], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.bd_pushing_prob_with_sweet, 100 }, - { - EL_PACMAN, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_PACMAN], 10 + EL_BDX_PLAYER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), + &li.bd_push_mega_rock_with_sweet, FALSE }, - { - EL_NUT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_NUT], 10 + EL_BDX_PLAYER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(6), + &li.bd_snap_element, EL_EMPTY }, { - EL_DYNAMITE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_DYNAMITE], 10 + EL_BDX_SAND_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_sand_looks_like, EL_BDX_SAND_1 }, { - EL_KEY_1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_KEY], 10 + EL_BDX_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_rock_turns_to_on_falling, EL_BDX_ROCK_FALLING }, - { - EL_PEARL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_PEARL], 10 + EL_BDX_ROCK, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_rock_turns_to_on_impact, EL_BDX_ROCK }, { - EL_CRYSTAL, -1, + EL_BDX_DIAMOND, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_CRYSTAL], 10 + &li.score[SC_DIAMOND_EXTRA], 20 + }, + { + EL_BDX_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_diamond_turns_to_on_falling, EL_BDX_DIAMOND_FALLING + }, + { + EL_BDX_DIAMOND, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_diamond_turns_to_on_impact, EL_BDX_DIAMOND }, { - EL_BD_AMOEBA, -1, + EL_BDX_FIREFLY_1, -1, TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &li.amoeba_content, EL_DIAMOND + &li.bd_firefly_1_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.amoeba_speed, 10 + EL_BDX_FIREFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_firefly_2_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_BD_AMOEBA, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.grow_into_diggable, TRUE + EL_BDX_BUTTERFLY_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_1_explodes_to, EL_BDX_DIAMOND_GROWING_1 }, { - EL_YAMYAM, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &li.yamyam_content, EL_ROCK, NULL, - &li.num_yamyam_contents, 4, MAX_ELEMENT_CONTENTS + EL_BDX_BUTTERFLY_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_butterfly_2_explodes_to, EL_BDX_DIAMOND_GROWING_1 }, + { - EL_YAMYAM, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_YAMYAM], 10 + EL_BDX_STONEFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_stonefly_explodes_to, EL_BDX_ROCK_GROWING_1 }, { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_ROBOT], 10 + EL_BDX_DRAGONFLY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_dragonfly_explodes_to, EL_BDX_EXPLODING_1 }, + { - EL_ROBOT, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.slurp_score, 10 + EL_BDX_DIAMOND_GROWING_5, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_diamond_birth_turns_to, EL_BDX_DIAMOND }, { - EL_ROBOT_WHEEL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_wheel, 10 + EL_BDX_BOMB_EXPLODING_4, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bomb_explosion_turns_to, EL_BDX_WALL }, { - EL_MAGIC_WALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_magic_wall, 10 + EL_BDX_NITRO_PACK_EXPLODING_4, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_nitro_explosion_turns_to, EL_EMPTY }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &li.game_of_life[0], 2 + EL_BDX_EXPLODING_5, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_explosion_turns_to, EL_EMPTY }, + { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &li.game_of_life[1], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_magic_wall_wait_hatching, FALSE }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(3), - &li.game_of_life[2], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_magic_wall_stops_amoeba, TRUE }, { - EL_GAME_OF_LIFE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(4), - &li.game_of_life[3], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_magic_wall_zero_infinite, TRUE }, { - EL_GAME_OF_LIFE, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), - &li.use_life_bugs, FALSE + EL_BDX_MAGIC_WALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_magic_wall_break_scan, FALSE }, - { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &li.biomaze[0], 2 + EL_BDX_MAGIC_WALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_magic_wall_time, 999 }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &li.biomaze[1], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_magic_wall_diamond_to, EL_BDX_ROCK_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(3), - &li.biomaze[2], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_magic_wall_rock_to, EL_BDX_DIAMOND_FALLING }, { - EL_BIOMAZE, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(4), - &li.biomaze[3], 3 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_magic_wall_mega_rock_to, EL_BDX_NITRO_PACK_FALLING }, - { - EL_TIMEGATE_SWITCH, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_timegate, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_magic_wall_nut_to, EL_BDX_NUT_FALLING }, - { - EL_LIGHT_SWITCH_ACTIVE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_light, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_magic_wall_nitro_pack_to, EL_BDX_MEGA_ROCK_FALLING }, - { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.shield_normal_time, 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &li.bd_magic_wall_flying_diamond_to, EL_BDX_FLYING_ROCK_FLYING }, { - EL_SHIELD_NORMAL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BDX_MAGIC_WALL, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(8), + &li.bd_magic_wall_flying_rock_to, EL_BDX_FLYING_DIAMOND_FLYING }, { - EL_SHIELD_DEADLY, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.shield_deadly_time, 10 + EL_BDX_CLOCK, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_clock_extra_time, 30 }, + { - EL_SHIELD_DEADLY, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.score[SC_SHIELD], 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_voodoo_collects_diamonds, FALSE }, - { - EL_EXTRA_TIME, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.extra_time, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_voodoo_hurt_kills_player, FALSE }, { - EL_EXTRA_TIME, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.extra_time_score, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_voodoo_dies_by_rock, FALSE }, - { - EL_TIME_ORB_FULL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.time_orb_time, 10 + EL_BDX_VOODOO_DOLL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_voodoo_vanish_by_explosion, TRUE }, { - EL_TIME_ORB_FULL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_time_orb_bug, FALSE + EL_BDX_VOODOO_DOLL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(5), + &li.bd_voodoo_penalty_time, 30 }, { - EL_SPRING, -1, + EL_BDX_SLIME, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.use_spring_bug, FALSE + &li.bd_slime_is_predictable, TRUE }, - { - EL_EMC_ANDROID, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.android_move_time, 10 + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_slime_permeability_rate, 100 }, { - EL_EMC_ANDROID, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.android_clone_time, 10 + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_slime_permeability_bits_c64, 0 }, { - EL_EMC_ANDROID, SAVE_CONF_NEVER, - TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), - &li.android_clone_element[0], EL_EMPTY, NULL, - &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS_OLD + EL_BDX_SLIME, -1, + TYPE_INTEGER, CONF_VALUE_32_BIT(1), + &li.bd_slime_random_seed_c64, -1 }, { - EL_EMC_ANDROID, -1, - TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), - &li.android_clone_element[0], EL_EMPTY, NULL, - &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_slime_eats_element_1, EL_BDX_DIAMOND }, - { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.lenses_score, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_slime_converts_to_element_1, EL_BDX_DIAMOND_FALLING }, { - EL_EMC_LENSES, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.lenses_time, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_slime_eats_element_2, EL_BDX_ROCK }, - { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.magnify_score, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_slime_converts_to_element_2, EL_BDX_ROCK_FALLING }, { - EL_EMC_MAGNIFIER, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &li.magnify_time, 10 + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_slime_eats_element_3, EL_BDX_NUT + }, + { + EL_BDX_SLIME, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_slime_converts_to_element_3, EL_BDX_NUT_FALLING }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.ball_time, 10 + EL_BDX_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_acid_eats_element, EL_BDX_SAND_1 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.ball_random, FALSE + EL_BDX_ACID, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_acid_spread_rate, 3 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.ball_active_initial, FALSE + EL_BDX_ACID, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(2), + &li.bd_acid_turns_to_element, EL_BDX_EXPLODING_3 + }, + + { + EL_BDX_BITER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_biter_move_delay, 0 }, { - EL_EMC_MAGIC_BALL, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &li.ball_content, EL_EMPTY, NULL, - &li.num_ball_contents, 4, MAX_ELEMENT_CONTENTS + EL_BDX_BITER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_biter_eats_element, EL_BDX_DIAMOND }, { - EL_SOKOBAN_FIELD_EMPTY, -1, + EL_BDX_BLADDER, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_bladder_converts_by_element, EL_BDX_VOODOO_DOLL + }, + + { + EL_BDX_EXPANDABLE_WALL_ANY, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.sb_fields_needed, TRUE + &li.bd_change_expanding_wall, FALSE + }, + { + EL_BDX_EXPANDABLE_WALL_ANY, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_expanding_wall_looks_like, EL_BDX_WALL }, { - EL_SOKOBAN_OBJECT, -1, + EL_BDX_REPLICATOR, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.sb_objects_needed, TRUE + &li.bd_replicators_active, TRUE + }, + { + EL_BDX_REPLICATOR, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_replicator_create_delay, 4 }, { - EL_MM_MCDUFFIN, -1, + EL_BDX_CONVEYOR_LEFT, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.mm_laser_red, FALSE + &li.bd_conveyor_belts_active, TRUE }, { - EL_MM_MCDUFFIN, -1, + EL_BDX_CONVEYOR_LEFT, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.mm_laser_green, FALSE + &li.bd_conveyor_belts_changed, FALSE }, + { - EL_MM_MCDUFFIN, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &li.mm_laser_blue, TRUE + EL_BDX_WATER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_water_cannot_flow_down, FALSE }, { - EL_DF_LASER, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &li.df_laser_red, TRUE + EL_BDX_NUT, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.bd_nut_content, EL_BDX_NUT_BREAKING_1 + }, + + { + EL_BDX_PNEUMATIC_HAMMER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_hammer_walls_break_delay, 5 }, { - EL_DF_LASER, -1, + EL_BDX_PNEUMATIC_HAMMER, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.df_laser_green, TRUE + &li.bd_hammer_walls_reappear, FALSE }, { - EL_DF_LASER, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &li.df_laser_blue, FALSE + EL_BDX_PNEUMATIC_HAMMER, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_hammer_walls_reappear_delay, 100 }, { - EL_MM_FUSE_ACTIVE, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_fuse, 25 + EL_BDX_ROCKET_LAUNCHER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_infinite_rockets, FALSE }, + { - EL_MM_BOMB, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_bomb, 75 + EL_BDX_SKELETON, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_num_skeletons_needed_for_pot, 5 }, { - EL_MM_GRAY_BALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_ball, 75 + EL_BDX_SKELETON, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_skeleton_worth_num_diamonds, 0 }, + { - EL_MM_STEEL_BLOCK, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.mm_time_block, 75 + EL_BDX_CREATURE_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.bd_creatures_start_backwards, FALSE }, { - EL_MM_LIGHTBALL, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_ELEM_BONUS], 10 + EL_BDX_CREATURE_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_creatures_turn_on_hatching, FALSE }, - - // ---------- unused values ------------------------------------------------- - { - EL_UNKNOWN, SAVE_CONF_NEVER, + EL_BDX_CREATURE_SWITCH, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), - &li.score[SC_UNKNOWN_15], 10 + &li.bd_creatures_auto_turn_delay, 0 }, { - -1, -1, - -1, -1, - NULL, -1 - } -}; - -static struct LevelFileConfigInfo chunk_config_NOTE[] = -{ - { - -1, -1, + EL_BDX_GRAVITY_SWITCH, -1, TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &xx_envelope.xsize, MAX_ENVELOPE_XSIZE, + &li.bd_gravity_direction, GD_MV_DOWN }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(2), - &xx_envelope.ysize, MAX_ENVELOPE_YSIZE, + EL_BDX_GRAVITY_SWITCH, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.bd_gravity_switch_active, FALSE }, - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &xx_envelope.autowrap, FALSE + EL_BDX_GRAVITY_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.bd_gravity_switch_delay, 10 }, { - -1, -1, + EL_BDX_GRAVITY_SWITCH, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), - &xx_envelope.centered, FALSE + &li.bd_gravity_affects_all, TRUE }, + // (the following values are related to various game elements) + { - -1, -1, - TYPE_STRING, CONF_VALUE_BYTES(1), - &xx_envelope.text, -1, NULL, - &xx_string_length_unused, -1, MAX_ENVELOPE_TEXT_LEN, - &xx_default_string_empty[0] + EL_EMERALD, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_EMERALD], 10 }, { - -1, -1, - -1, -1, - NULL, -1 - } -}; + EL_DIAMOND, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_DIAMOND], 10 + }, -static struct LevelFileConfigInfo chunk_config_CUSX_base[] = -{ { - -1, -1, - TYPE_STRING, CONF_VALUE_BYTES(1), - &xx_ei.description[0], -1, - &yy_ei.description[0], - &xx_string_length_unused, -1, MAX_ELEMENT_NAME_LEN, - &xx_default_description[0] + EL_BUG, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_BUG], 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(1), - &xx_ei.properties[EP_BITFIELD_BASE_NR], EP_BITMASK_BASE_DEFAULT, - &yy_ei.properties[EP_BITFIELD_BASE_NR] + EL_SPACESHIP, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_SPACESHIP], 10 }, -#if ENABLE_RESERVED_CODE - // (reserved for later use) + { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(2), - &xx_ei.properties[EP_BITFIELD_BASE_NR + 1], EP_BITMASK_DEFAULT, - &yy_ei.properties[EP_BITFIELD_BASE_NR + 1] + EL_PACMAN, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_PACMAN], 10 }, -#endif { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), - &xx_ei.use_gfx_element, FALSE, - &yy_ei.use_gfx_element + EL_NUT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_NUT], 10 }, + { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_ei.gfx_element_initial, EL_EMPTY_SPACE, - &yy_ei.gfx_element_initial + EL_DYNAMITE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_DYNAMITE], 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(2), - &xx_ei.access_direction, MV_ALL_DIRECTIONS, - &yy_ei.access_direction + EL_KEY_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_KEY], 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &xx_ei.collect_score_initial, 10, - &yy_ei.collect_score_initial + EL_PEARL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_PEARL], 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(3), - &xx_ei.collect_count_initial, 1, - &yy_ei.collect_count_initial + EL_CRYSTAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_CRYSTAL], 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(4), - &xx_ei.ce_value_fixed_initial, 0, - &yy_ei.ce_value_fixed_initial + EL_BD_AMOEBA, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &li.amoeba_content, EL_DIAMOND }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(5), - &xx_ei.ce_value_random_initial, 0, - &yy_ei.ce_value_random_initial + EL_BD_AMOEBA, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.amoeba_speed, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), - &xx_ei.use_last_ce_value, FALSE, - &yy_ei.use_last_ce_value + EL_BD_AMOEBA, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.grow_into_diggable, TRUE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(6), - &xx_ei.push_delay_fixed, 8, - &yy_ei.push_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_amoeba_1_threshold_too_big, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(7), - &xx_ei.push_delay_random, 8, - &yy_ei.push_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.bd_amoeba_1_slow_growth_time, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(8), - &xx_ei.drop_delay_fixed, 0, - &yy_ei.drop_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_1_content_too_big, EL_BDX_ROCK }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(9), - &xx_ei.drop_delay_random, 0, - &yy_ei.drop_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_1_content_enclosed, EL_BDX_DIAMOND }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(10), - &xx_ei.move_delay_fixed, 0, - &yy_ei.move_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_amoeba_1_slow_growth_rate, 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(11), - &xx_ei.move_delay_random, 0, - &yy_ei.move_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_amoeba_1_fast_growth_rate, 25 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(16), - &xx_ei.step_delay_fixed, 0, - &yy_ei.step_delay_fixed + EL_BDX_AMOEBA_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_amoeba_wait_for_hatching, FALSE }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(17), - &xx_ei.step_delay_random, 0, - &yy_ei.step_delay_random + EL_BDX_AMOEBA_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &li.bd_amoeba_start_immediately, TRUE }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_ei.move_pattern, MV_ALL_DIRECTIONS, - &yy_ei.move_pattern + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.bd_amoeba_2_threshold_too_big, 200 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(4), - &xx_ei.move_direction_initial, MV_START_AUTOMATIC, - &yy_ei.move_direction_initial + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.bd_amoeba_2_slow_growth_time, 200 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(5), - &xx_ei.move_stepsize, TILEX / 8, - &yy_ei.move_stepsize + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(3), + &li.bd_amoeba_2_content_too_big, EL_BDX_ROCK }, - { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(12), - &xx_ei.move_enter_element, EL_EMPTY_SPACE, - &yy_ei.move_enter_element + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(4), + &li.bd_amoeba_2_content_enclosed, EL_BDX_DIAMOND }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(13), - &xx_ei.move_leave_element, EL_EMPTY_SPACE, - &yy_ei.move_leave_element + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &li.bd_amoeba_2_content_exploding, EL_EMPTY }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(6), - &xx_ei.move_leave_type, LEAVE_TYPE_UNLIMITED, - &yy_ei.move_leave_type + EL_BDX_AMOEBA_2, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(6), + &li.bd_amoeba_2_content_looks_like, EL_BDX_AMOEBA_2 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(7), - &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, - &yy_ei.slippery_type + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.bd_amoeba_2_slow_growth_rate, 3 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(8), - &xx_ei.explosion_type, EXPLODES_3X3, - &yy_ei.explosion_type + EL_BDX_AMOEBA_2, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.bd_amoeba_2_fast_growth_rate, 25 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(14), - &xx_ei.explosion_delay, 16, - &yy_ei.explosion_delay + EL_BDX_AMOEBA_2, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.bd_amoeba_2_explode_by_amoeba, TRUE }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(15), - &xx_ei.ignition_delay, 8, - &yy_ei.ignition_delay + EL_YAMYAM, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &li.yamyam_content, EL_ROCK, NULL, + &li.num_yamyam_contents, 4, MAX_ELEMENT_CONTENTS }, - { - -1, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(2), - &xx_ei.content, EL_EMPTY_SPACE, - &yy_ei.content, - &xx_num_contents, 1, 1 + EL_YAMYAM, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_YAMYAM], 10 }, - // ---------- "num_change_pages" must be the last entry --------------------- - { - -1, SAVE_CONF_ALWAYS, - TYPE_INTEGER, CONF_VALUE_8_BIT(9), - &xx_ei.num_change_pages, 1, - &yy_ei.num_change_pages + EL_ROBOT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_ROBOT], 10 }, - { - -1, -1, - -1, -1, - NULL, -1, - NULL - } -}; - -static struct LevelFileConfigInfo chunk_config_CUSX_change[] = -{ - // ---------- "current_change_page" must be the first entry ----------------- + EL_ROBOT, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.slurp_score, 10 + }, { - -1, SAVE_CONF_ALWAYS, - TYPE_INTEGER, CONF_VALUE_8_BIT(1), - &xx_current_change_page, -1 + EL_ROBOT_WHEEL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_wheel, 10 }, - // ---------- (the remaining entries can be in any order) ------------------- - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &xx_change.can_change, FALSE + EL_MAGIC_WALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_magic_wall, 10 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(1), - &xx_event_bits[0], 0 + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.game_of_life[0], 2 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(2), - &xx_event_bits[1], 0 + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.game_of_life[1], 3 }, - { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(3), - &xx_change.trigger_player, CH_PLAYER_ANY + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.game_of_life[2], 3 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_8_BIT(4), - &xx_change.trigger_side, CH_SIDE_ANY + EL_GAME_OF_LIFE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.game_of_life[3], 3 }, { - -1, -1, - TYPE_BITFIELD, CONF_VALUE_32_BIT(3), - &xx_change.trigger_page, CH_PAGE_ANY + EL_GAME_OF_LIFE, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(5), + &li.use_life_bugs, FALSE }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(1), - &xx_change.target_element, EL_EMPTY_SPACE + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.biomaze[0], 2 }, - { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(2), - &xx_change.delay_fixed, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &li.biomaze[1], 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(3), - &xx_change.delay_random, 0 + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(3), + &li.biomaze[2], 3 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(4), - &xx_change.delay_frames, FRAMES_PER_SECOND + EL_BIOMAZE, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(4), + &li.biomaze[3], 3 }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(5), - &xx_change.initial_trigger_element, EL_EMPTY_SPACE + EL_TIMEGATE_SWITCH, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_timegate, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(6), - &xx_change.explode, FALSE - }, - { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(7), - &xx_change.use_target_content, FALSE + EL_LIGHT_SWITCH_ACTIVE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_light, 10 }, + { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(8), - &xx_change.only_if_complete, FALSE + EL_SHIELD_NORMAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.shield_normal_time, 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(9), - &xx_change.use_random_replace, FALSE + EL_SHIELD_NORMAL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.score[SC_SHIELD], 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(10), - &xx_change.random_percentage, 100 + EL_SHIELD_DEADLY, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.shield_deadly_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(11), - &xx_change.replace_when, CP_WHEN_EMPTY + EL_SHIELD_DEADLY, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.score[SC_SHIELD], 10 }, { - -1, -1, - TYPE_BOOLEAN, CONF_VALUE_8_BIT(12), - &xx_change.has_action, FALSE + EL_EXTRA_TIME, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.extra_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(13), - &xx_change.action_type, CA_NO_ACTION + EL_EXTRA_TIME, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.extra_time_score, 10 }, + { - -1, -1, - TYPE_INTEGER, CONF_VALUE_8_BIT(14), - &xx_change.action_mode, CA_MODE_UNDEFINED + EL_TIME_ORB_FULL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.time_orb_time, 10 }, { - -1, -1, - TYPE_INTEGER, CONF_VALUE_16_BIT(6), - &xx_change.action_arg, CA_ARG_UNDEFINED + EL_TIME_ORB_FULL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.use_time_orb_bug, FALSE }, { - -1, -1, - TYPE_ELEMENT, CONF_VALUE_16_BIT(7), - &xx_change.action_element, EL_EMPTY_SPACE + EL_SPRING, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.use_spring_bug, FALSE }, { - -1, -1, - TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), - &xx_change.target_content, EL_EMPTY_SPACE, NULL, - &xx_num_contents, 1, 1 - }, + EL_EMC_ANDROID, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.android_move_time, 10 + }, + { + EL_EMC_ANDROID, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.android_clone_time, 10 + }, + { + EL_EMC_ANDROID, SAVE_CONF_NEVER, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), + &li.android_clone_element[0], EL_EMPTY, NULL, + &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS_OLD + }, + { + EL_EMC_ANDROID, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(2), + &li.android_clone_element[0], EL_EMPTY, NULL, + &li.num_android_clone_elements, 1, MAX_ANDROID_ELEMENTS + }, + + { + EL_EMC_LENSES, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.lenses_score, 10 + }, + { + EL_EMC_LENSES, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.lenses_time, 10 + }, + + { + EL_EMC_MAGNIFIER, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.magnify_score, 10 + }, + { + EL_EMC_MAGNIFIER, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &li.magnify_time, 10 + }, + + { + EL_EMC_MAGIC_BALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.ball_time, 10 + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.ball_random, FALSE + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.ball_active_initial, FALSE + }, + { + EL_EMC_MAGIC_BALL, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &li.ball_content, EL_EMPTY, NULL, + &li.num_ball_contents, 4, MAX_ELEMENT_CONTENTS + }, + + { + EL_SOKOBAN_FIELD_EMPTY, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.sb_fields_needed, TRUE + }, + + { + EL_SOKOBAN_OBJECT, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.sb_objects_needed, TRUE + }, + + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.mm_laser_red, FALSE + }, + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.mm_laser_green, FALSE + }, + { + EL_MM_MCDUFFIN, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.mm_laser_blue, TRUE + }, + + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &li.df_laser_red, TRUE + }, + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.df_laser_green, TRUE + }, + { + EL_DF_LASER, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.df_laser_blue, FALSE + }, + + { + EL_MM_FUSE_ACTIVE, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_fuse, 25 + }, + { + EL_MM_BOMB, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_bomb, 75 + }, + + { + EL_MM_GRAY_BALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_ball, 75 + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.mm_ball_choice_mode, ANIM_RANDOM + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), + &li.mm_ball_content, EL_EMPTY, NULL, + &li.num_mm_ball_contents, 8, MAX_MM_BALL_CONTENTS + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.rotate_mm_ball_content, TRUE + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.explode_mm_ball, FALSE + }, + + { + EL_MM_STEEL_BLOCK, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.mm_time_block, 75 + }, + { + EL_MM_LIGHTBALL, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(1), + &li.score[SC_ELEM_BONUS], 10 + }, + + { + -1, -1, + -1, -1, + NULL, -1 + } +}; + +static struct LevelFileConfigInfo chunk_config_NOTE[] = +{ + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &xx_envelope.xsize, MAX_ENVELOPE_XSIZE, + }, + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(2), + &xx_envelope.ysize, MAX_ENVELOPE_YSIZE, + }, + + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &xx_envelope.autowrap, FALSE + }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(4), + &xx_envelope.centered, FALSE + }, + + { + -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] + }, + + { + -1, -1, + -1, -1, + NULL, -1 + } +}; + +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] + }, + + { + -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) + { + -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 + + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE, + &yy_ei.use_gfx_element + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE, + &yy_ei.gfx_element_initial + }, + + { + -1, -1, + TYPE_BITFIELD, CONF_VALUE_8_BIT(2), + &xx_ei.access_direction, MV_ALL_DIRECTIONS, + &yy_ei.access_direction + }, + + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_16_BIT(2), + &xx_ei.collect_score_initial, 10, + &yy_ei.collect_score_initial + }, + { + -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, + 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 + }, + + { + -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 + }, + + { + -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 + }, + + { + -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 + }, + + { + -1, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(7), + &xx_ei.slippery_type, SLIPPERY_ANY_RANDOM, + &yy_ei.slippery_type + }, + + { + -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 + }, + + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(2), + &xx_ei.content, EL_EMPTY_SPACE, + &yy_ei.content, + &xx_num_contents, 1, 1 + }, + + // ---------- "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 + }, + + { + -1, -1, + -1, -1, + NULL, -1, + NULL + } +}; + +static struct LevelFileConfigInfo chunk_config_CUSX_change[] = +{ + // ---------- "current_change_page" must be the first entry ----------------- + + { + -1, SAVE_CONF_ALWAYS, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &xx_current_change_page, -1 + }, + + // ---------- (the remaining entries can be in any order) ------------------- + + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &xx_change.can_change, FALSE + }, + + { + -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 + }, + + { + -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 + }, + + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_change.target_element, EL_EMPTY_SPACE + }, + + { + -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 + }, + + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(5), + &xx_change.initial_trigger_element, EL_EMPTY_SPACE + }, + + { + -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 + }, + + { + -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 + }, + + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(7), + &xx_change.action_element, EL_EMPTY_SPACE + }, + + { + -1, -1, + TYPE_CONTENT_LIST, CONF_VALUE_BYTES(1), + &xx_change.target_content, EL_EMPTY_SPACE, NULL, + &xx_num_contents, 1, 1 + }, { -1, -1, @@ -1376,6 +1973,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] = } }; +static struct LevelFileConfigInfo chunk_config_EMPX[] = +{ + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + }, + + { + -1, -1, + -1, -1, + NULL, -1 + } +}; + static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE) { { @@ -1721,16 +2338,23 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) 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; @@ -1754,19 +2378,49 @@ static void setLevelInfoToDefaults_Level(struct LevelInfo *level) 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++) - level->field[x][y] = EL_SAND; + { + 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[0][0] = EL_PLAYER_1; - level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED; + level->field[x1][y1] = getEngineElement(EL_PLAYER_1); + level->field[x2][y2] = getEngineElement(EL_EXIT_CLOSED); - BorderElement = EL_STEELWALL; + 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; @@ -1820,6 +2474,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) @@ -1849,8 +2513,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); @@ -1893,6 +2556,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; @@ -2002,6 +2675,32 @@ static void ActivateLevelTemplate(void) } } +boolean isLevelsetFilename_BD(char *filename) +{ + return (strSuffixLower(filename, ".bd") || + strSuffixLower(filename, ".bdr") || + strSuffixLower(filename, ".brc") || + strSuffixLower(filename, ".gds")); +} + +static boolean checkForPackageFromBasename_BD(char *basename) +{ + // check for native BD level file extensions + if (!isLevelsetFilename_BD(basename)) + 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; @@ -2036,6 +2735,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); @@ -2295,6 +2998,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); @@ -2814,9 +3522,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)); @@ -2825,7 +3534,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); @@ -2952,7 +3661,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)); @@ -2993,7 +3702,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; } @@ -3322,6 +4031,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 @@ -3351,6 +4064,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 @@ -3371,6 +4087,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) @@ -3493,6 +4233,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 } }; @@ -3526,6 +4267,14 @@ 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 @@ -3533,6 +4282,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { Warn("wrong size (%d) of chunk '%s' in level file '%s'", chunk_size, chunk_name, filename); + + break; } } } @@ -3542,6 +4293,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->bd_magic_wall_time; + cave->magic_timer_zero_is_infinite = level->bd_magic_wall_zero_infinite; + cave->magic_timer_wait_for_hatching = level->bd_magic_wall_wait_hatching; + cave->magic_wall_stops_amoeba = level->bd_magic_wall_stops_amoeba; + cave->magic_wall_breakscan = level->bd_magic_wall_break_scan; + + cave->magic_diamond_to = LEVEL_TO_CAVE(level->bd_magic_wall_diamond_to); + cave->magic_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_rock_to); + cave->magic_mega_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_mega_rock_to); + cave->magic_nut_to = LEVEL_TO_CAVE(level->bd_magic_wall_nut_to); + cave->magic_nitro_pack_to = LEVEL_TO_CAVE(level->bd_magic_wall_nitro_pack_to); + cave->magic_flying_diamond_to = LEVEL_TO_CAVE(level->bd_magic_wall_flying_diamond_to); + cave->magic_flying_stone_to = LEVEL_TO_CAVE(level->bd_magic_wall_flying_rock_to); + + cave->amoeba_timer_wait_for_hatching = level->bd_amoeba_wait_for_hatching; + cave->amoeba_timer_started_immediately= level->bd_amoeba_start_immediately; + cave->amoeba_2_explodes_by_amoeba = level->bd_amoeba_2_explode_by_amoeba; + cave->level_amoeba_threshold[0] = level->bd_amoeba_1_threshold_too_big; + cave->level_amoeba_time[0] = level->bd_amoeba_1_slow_growth_time; + cave->amoeba_growth_prob = level->bd_amoeba_1_slow_growth_rate * 10000; + cave->amoeba_fast_growth_prob = level->bd_amoeba_1_fast_growth_rate * 10000; + cave->level_amoeba_2_threshold[0] = level->bd_amoeba_2_threshold_too_big; + cave->level_amoeba_2_time[0] = level->bd_amoeba_2_slow_growth_time; + cave->amoeba_2_growth_prob = level->bd_amoeba_2_slow_growth_rate * 10000; + cave->amoeba_2_fast_growth_prob = level->bd_amoeba_2_fast_growth_rate * 10000; + + cave->amoeba_too_big_effect = LEVEL_TO_CAVE(level->bd_amoeba_1_content_too_big); + cave->amoeba_enclosed_effect = LEVEL_TO_CAVE(level->bd_amoeba_1_content_enclosed); + cave->amoeba_2_too_big_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_too_big); + cave->amoeba_2_enclosed_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_enclosed); + cave->amoeba_2_explosion_effect = LEVEL_TO_CAVE(level->bd_amoeba_2_content_exploding); + cave->amoeba_2_looks_like = LEVEL_TO_CAVE(level->bd_amoeba_2_content_looks_like); + + cave->slime_predictable = level->bd_slime_is_predictable; + cave->slime_correct_random = level->bd_slime_correct_random; + cave->level_slime_permeability[0] = level->bd_slime_permeability_rate * 10000; + cave->level_slime_permeability_c64[0] = level->bd_slime_permeability_bits_c64; + cave->level_slime_seed_c64[0] = level->bd_slime_random_seed_c64; + cave->level_rand[0] = level->bd_cave_random_seed_c64; + cave->slime_eats_1 = LEVEL_TO_CAVE(level->bd_slime_eats_element_1); + cave->slime_converts_1 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_1); + cave->slime_eats_2 = LEVEL_TO_CAVE(level->bd_slime_eats_element_2); + cave->slime_converts_2 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_2); + cave->slime_eats_3 = LEVEL_TO_CAVE(level->bd_slime_eats_element_3); + cave->slime_converts_3 = LEVEL_TO_CAVE(level->bd_slime_converts_to_element_3); + + cave->acid_eats_this = LEVEL_TO_CAVE(level->bd_acid_eats_element); + cave->acid_spread_ratio = level->bd_acid_spread_rate * 10000; + cave->acid_turns_to = LEVEL_TO_CAVE(level->bd_acid_turns_to_element); + + cave->biter_delay_frame = level->bd_biter_move_delay; + cave->biter_eat = LEVEL_TO_CAVE(level->bd_biter_eats_element); + + cave->bladder_converts_by = LEVEL_TO_CAVE(level->bd_bladder_converts_by_element); + + cave->expanding_wall_changed = level->bd_change_expanding_wall; + + cave->replicators_active = level->bd_replicators_active; + cave->replicator_delay_frame = level->bd_replicator_create_delay; + + cave->conveyor_belts_active = level->bd_conveyor_belts_active; + cave->conveyor_belts_direction_changed= level->bd_conveyor_belts_changed; + + cave->water_does_not_flow_down = level->bd_water_cannot_flow_down; + + cave->nut_turns_to_when_crushed = LEVEL_TO_CAVE(level->bd_nut_content); + + cave->pneumatic_hammer_frame = level->bd_hammer_walls_break_delay; + cave->hammered_walls_reappear = level->bd_hammer_walls_reappear; + cave->hammered_wall_reappear_frame = level->bd_hammer_walls_reappear_delay; + + cave->infinite_rockets = level->bd_infinite_rockets; + + cave->skeletons_needed_for_pot = level->bd_num_skeletons_needed_for_pot; + cave->skeletons_worth_diamonds = level->bd_skeleton_worth_num_diamonds; + + cave->expanding_wall_looks_like = LEVEL_TO_CAVE(level->bd_expanding_wall_looks_like); + cave->dirt_looks_like = LEVEL_TO_CAVE(level->bd_sand_looks_like); + + cave->creatures_backwards = level->bd_creatures_start_backwards; + cave->creatures_direction_auto_change_on_start = level->bd_creatures_turn_on_hatching; + cave->creatures_direction_auto_change_time = level->bd_creatures_auto_turn_delay; + + cave->gravity = level->bd_gravity_direction; + cave->gravity_switch_active = level->bd_gravity_switch_active; + cave->gravity_change_time = level->bd_gravity_switch_delay; + cave->gravity_affects_all = level->bd_gravity_affects_all; + + cave->stone_falling_effect = LEVEL_TO_CAVE(level->bd_rock_turns_to_on_falling); + cave->stone_bouncing_effect = LEVEL_TO_CAVE(level->bd_rock_turns_to_on_impact); + cave->diamond_falling_effect = LEVEL_TO_CAVE(level->bd_diamond_turns_to_on_falling); + cave->diamond_bouncing_effect = LEVEL_TO_CAVE(level->bd_diamond_turns_to_on_impact); + + cave->firefly_explode_to = LEVEL_TO_CAVE(level->bd_firefly_1_explodes_to); + cave->alt_firefly_explode_to = LEVEL_TO_CAVE(level->bd_firefly_2_explodes_to); + cave->butterfly_explode_to = LEVEL_TO_CAVE(level->bd_butterfly_1_explodes_to); + cave->alt_butterfly_explode_to = LEVEL_TO_CAVE(level->bd_butterfly_2_explodes_to); + cave->stonefly_explode_to = LEVEL_TO_CAVE(level->bd_stonefly_explodes_to); + cave->dragonfly_explode_to = LEVEL_TO_CAVE(level->bd_dragonfly_explodes_to); + + cave->diamond_birth_effect = LEVEL_TO_CAVE(level->bd_diamond_birth_turns_to); + cave->bomb_explosion_effect = LEVEL_TO_CAVE(level->bd_bomb_explosion_turns_to); + cave->nitro_explosion_effect = LEVEL_TO_CAVE(level->bd_nitro_explosion_turns_to); + cave->explosion_effect = LEVEL_TO_CAVE(level->bd_explosion_turns_to); + + cave->colorb = level->bd_color_b; + cave->color0 = level->bd_color_0; + cave->color1 = level->bd_color_1; + cave->color2 = level->bd_color_2; + cave->color3 = level->bd_color_3; + cave->color4 = level->bd_color_4; + cave->color5 = level->bd_color_5; + + // level name + strncpy(cave->name, level->name, sizeof(GdString)); + cave->name[sizeof(GdString) - 1] = '\0'; + + // playfield elements + for (x = 0; x < cave->w; x++) + for (y = 0; y < cave->h; y++) + cave->map[y][x] = LEVEL_TO_CAVE(level->field[x][y]); +} + +static void CopyNativeLevel_BD_to_RND(struct LevelInfo *level) +{ + struct LevelInfo_BD *level_bd = level->native_bd_level; + GdCave *cave = level_bd->cave; + int bd_level_nr = level_bd->level_nr; + int x, y; + + level->fieldx = MIN(cave->w, MAX_LEV_FIELDX); + level->fieldy = MIN(cave->h, MAX_LEV_FIELDY); + + // level type + level->bd_intermission = cave->intermission; + + // level settings + level->time = cave->level_time[bd_level_nr]; + level->gems_needed = cave->level_diamonds[bd_level_nr]; + + // game timing + level->bd_scheduling_type = cave->scheduling; + level->bd_pal_timing = cave->pal_timing; + level->bd_cycle_delay_ms = cave->level_speed[bd_level_nr]; + level->bd_cycle_delay_c64 = cave->level_ckdelay[bd_level_nr]; + level->bd_hatching_delay_cycles = cave->level_hatching_delay_frame[bd_level_nr]; + level->bd_hatching_delay_seconds = cave->level_hatching_delay_time[bd_level_nr]; + + // scores + level->score[SC_TIME_BONUS] = cave->level_timevalue[bd_level_nr]; + level->score[SC_EMERALD] = cave->diamond_value; + level->score[SC_DIAMOND_EXTRA] = cave->extra_diamond_value; + + // compatibility settings + level->bd_line_shifting_borders = cave->lineshift; + level->bd_scan_first_and_last_row = cave->border_scan_first_and_last; + level->bd_short_explosions = cave->short_explosions; + + // player properties + level->bd_diagonal_movements = cave->diagonal_movements; + level->bd_topmost_player_active = cave->active_is_first_found; + level->bd_pushing_prob = cave->pushing_stone_prob / 10000; + level->bd_pushing_prob_with_sweet = cave->pushing_stone_prob_sweet / 10000; + level->bd_push_mega_rock_with_sweet = cave->mega_stones_pushable_with_sweet; + level->bd_snap_element = CAVE_TO_LEVEL(cave->snap_element); + + // element properties + level->bd_clock_extra_time = cave->level_bonus_time[bd_level_nr]; + level->bd_voodoo_collects_diamonds = cave->voodoo_collects_diamonds; + level->bd_voodoo_hurt_kills_player = cave->voodoo_any_hurt_kills_player; + level->bd_voodoo_dies_by_rock = cave->voodoo_dies_by_stone; + level->bd_voodoo_vanish_by_explosion = cave->voodoo_disappear_in_explosion; + level->bd_voodoo_penalty_time = cave->level_penalty_time[bd_level_nr]; + level->bd_magic_wall_time = cave->level_magic_wall_time[bd_level_nr]; + level->bd_magic_wall_zero_infinite = cave->magic_timer_zero_is_infinite; + level->bd_magic_wall_wait_hatching = cave->magic_timer_wait_for_hatching; + level->bd_magic_wall_stops_amoeba = cave->magic_wall_stops_amoeba; + level->bd_magic_wall_break_scan = cave->magic_wall_breakscan; + + level->bd_magic_wall_diamond_to = CAVE_TO_LEVEL(cave->magic_diamond_to); + level->bd_magic_wall_rock_to = CAVE_TO_LEVEL(cave->magic_stone_to); + level->bd_magic_wall_mega_rock_to = CAVE_TO_LEVEL(cave->magic_mega_stone_to); + level->bd_magic_wall_nut_to = CAVE_TO_LEVEL(cave->magic_nut_to); + level->bd_magic_wall_nitro_pack_to = CAVE_TO_LEVEL(cave->magic_nitro_pack_to); + level->bd_magic_wall_flying_diamond_to= CAVE_TO_LEVEL(cave->magic_flying_diamond_to); + level->bd_magic_wall_flying_rock_to = CAVE_TO_LEVEL(cave->magic_flying_stone_to); + + level->bd_amoeba_wait_for_hatching = cave->amoeba_timer_wait_for_hatching; + level->bd_amoeba_start_immediately = cave->amoeba_timer_started_immediately; + level->bd_amoeba_2_explode_by_amoeba = cave->amoeba_2_explodes_by_amoeba; + level->bd_amoeba_1_threshold_too_big = cave->level_amoeba_threshold[bd_level_nr]; + level->bd_amoeba_1_slow_growth_time = cave->level_amoeba_time[bd_level_nr]; + level->bd_amoeba_1_slow_growth_rate = cave->amoeba_growth_prob / 10000; + level->bd_amoeba_1_fast_growth_rate = cave->amoeba_fast_growth_prob / 10000; + level->bd_amoeba_2_threshold_too_big = cave->level_amoeba_2_threshold[bd_level_nr]; + level->bd_amoeba_2_slow_growth_time = cave->level_amoeba_2_time[bd_level_nr]; + level->bd_amoeba_2_slow_growth_rate = cave->amoeba_2_growth_prob / 10000; + level->bd_amoeba_2_fast_growth_rate = cave->amoeba_2_fast_growth_prob / 10000; + + level->bd_amoeba_1_content_too_big = CAVE_TO_LEVEL(cave->amoeba_too_big_effect); + level->bd_amoeba_1_content_enclosed = CAVE_TO_LEVEL(cave->amoeba_enclosed_effect); + level->bd_amoeba_2_content_too_big = CAVE_TO_LEVEL(cave->amoeba_2_too_big_effect); + level->bd_amoeba_2_content_enclosed = CAVE_TO_LEVEL(cave->amoeba_2_enclosed_effect); + level->bd_amoeba_2_content_exploding = CAVE_TO_LEVEL(cave->amoeba_2_explosion_effect); + level->bd_amoeba_2_content_looks_like = CAVE_TO_LEVEL(cave->amoeba_2_looks_like); + + level->bd_slime_is_predictable = cave->slime_predictable; + level->bd_slime_correct_random = cave->slime_correct_random; + level->bd_slime_permeability_rate = cave->level_slime_permeability[bd_level_nr] / 10000; + level->bd_slime_permeability_bits_c64 = cave->level_slime_permeability_c64[bd_level_nr]; + level->bd_slime_random_seed_c64 = cave->level_slime_seed_c64[bd_level_nr]; + level->bd_cave_random_seed_c64 = cave->level_rand[bd_level_nr]; + level->bd_slime_eats_element_1 = CAVE_TO_LEVEL(cave->slime_eats_1); + level->bd_slime_converts_to_element_1 = CAVE_TO_LEVEL(cave->slime_converts_1); + level->bd_slime_eats_element_2 = CAVE_TO_LEVEL(cave->slime_eats_2); + level->bd_slime_converts_to_element_2 = CAVE_TO_LEVEL(cave->slime_converts_2); + level->bd_slime_eats_element_3 = CAVE_TO_LEVEL(cave->slime_eats_3); + level->bd_slime_converts_to_element_3 = CAVE_TO_LEVEL(cave->slime_converts_3); + + level->bd_acid_eats_element = CAVE_TO_LEVEL(cave->acid_eats_this); + level->bd_acid_spread_rate = cave->acid_spread_ratio / 10000; + level->bd_acid_turns_to_element = CAVE_TO_LEVEL(cave->acid_turns_to); + + level->bd_biter_move_delay = cave->biter_delay_frame; + level->bd_biter_eats_element = CAVE_TO_LEVEL(cave->biter_eat); + + level->bd_bladder_converts_by_element = CAVE_TO_LEVEL(cave->bladder_converts_by); + + level->bd_change_expanding_wall = cave->expanding_wall_changed; + + level->bd_replicators_active = cave->replicators_active; + level->bd_replicator_create_delay = cave->replicator_delay_frame; + + level->bd_conveyor_belts_active = cave->conveyor_belts_active; + level->bd_conveyor_belts_changed = cave->conveyor_belts_direction_changed; + + level->bd_water_cannot_flow_down = cave->water_does_not_flow_down; + + level->bd_nut_content = CAVE_TO_LEVEL(cave->nut_turns_to_when_crushed); + + level->bd_hammer_walls_break_delay = cave->pneumatic_hammer_frame; + level->bd_hammer_walls_reappear = cave->hammered_walls_reappear; + level->bd_hammer_walls_reappear_delay = cave->hammered_wall_reappear_frame; + + level->bd_infinite_rockets = cave->infinite_rockets; + + level->bd_num_skeletons_needed_for_pot= cave->skeletons_needed_for_pot; + level->bd_skeleton_worth_num_diamonds = cave->skeletons_worth_diamonds; + + level->bd_expanding_wall_looks_like = CAVE_TO_LEVEL(cave->expanding_wall_looks_like); + level->bd_sand_looks_like = CAVE_TO_LEVEL(cave->dirt_looks_like); + + level->bd_creatures_start_backwards = cave->creatures_backwards; + level->bd_creatures_turn_on_hatching = cave->creatures_direction_auto_change_on_start; + level->bd_creatures_auto_turn_delay = cave->creatures_direction_auto_change_time; + + level->bd_gravity_direction = cave->gravity; + level->bd_gravity_switch_active = cave->gravity_switch_active; + level->bd_gravity_switch_delay = cave->gravity_change_time; + level->bd_gravity_affects_all = cave->gravity_affects_all; + + level->bd_rock_turns_to_on_falling = CAVE_TO_LEVEL(cave->stone_falling_effect); + level->bd_rock_turns_to_on_impact = CAVE_TO_LEVEL(cave->stone_bouncing_effect); + level->bd_diamond_turns_to_on_falling = CAVE_TO_LEVEL(cave->diamond_falling_effect); + level->bd_diamond_turns_to_on_impact = CAVE_TO_LEVEL(cave->diamond_bouncing_effect); + + level->bd_firefly_1_explodes_to = CAVE_TO_LEVEL(cave->firefly_explode_to); + level->bd_firefly_2_explodes_to = CAVE_TO_LEVEL(cave->alt_firefly_explode_to); + level->bd_butterfly_1_explodes_to = CAVE_TO_LEVEL(cave->butterfly_explode_to); + level->bd_butterfly_2_explodes_to = CAVE_TO_LEVEL(cave->alt_butterfly_explode_to); + level->bd_stonefly_explodes_to = CAVE_TO_LEVEL(cave->stonefly_explode_to); + level->bd_dragonfly_explodes_to = CAVE_TO_LEVEL(cave->dragonfly_explode_to); + + level->bd_diamond_birth_turns_to = CAVE_TO_LEVEL(cave->diamond_birth_effect); + level->bd_bomb_explosion_turns_to = CAVE_TO_LEVEL(cave->bomb_explosion_effect); + level->bd_nitro_explosion_turns_to = CAVE_TO_LEVEL(cave->nitro_explosion_effect); + level->bd_explosion_turns_to = CAVE_TO_LEVEL(cave->explosion_effect); + + level->bd_color_b = cave->colorb; + level->bd_color_0 = cave->color0; + level->bd_color_1 = cave->color1; + level->bd_color_2 = cave->color2; + level->bd_color_3 = cave->color3; + level->bd_color_4 = cave->color4; + level->bd_color_5 = cave->color5; + + // set default color type and colors for BD style level colors + SetDefaultLevelColorType_BD(); + SetDefaultLevelColors_BD(); + + // level name + char *cave_name = getStringPrint("%s / %d", cave->name, bd_level_nr + 1); + + strncpy(level->name, cave_name, MAX_LEVEL_NAME_LEN); + level->name[MAX_LEVEL_NAME_LEN] = '\0'; + + // playfield elements + for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + level->field[x][y] = CAVE_TO_LEVEL(cave->map[y][x]); + + 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 // ---------------------------------------------------------------------------- @@ -3606,6 +4791,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) cav->lenses_time = level->lenses_time; cav->magnify_time = level->magnify_time; + cav->wind_time = 9999; cav->wind_direction = map_direction_RND_to_EM(level->wind_direction_initial); @@ -3999,8 +5185,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; @@ -4055,7 +5239,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); @@ -4064,9 +5248,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); @@ -4083,6 +5271,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] = @@ -4092,7 +5289,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); @@ -4101,9 +5298,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); @@ -4123,6 +5324,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]); @@ -5604,8 +6814,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; @@ -5854,7 +7063,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, } } - LoadLevelFromFileStream_DC(file, level, level_file_info->nr); + LoadLevelFromFileStream_DC(file, level); closeFile(file); } @@ -5927,7 +7136,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'; @@ -5969,10 +7177,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') @@ -6157,6 +7361,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) @@ -6189,7 +7407,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); @@ -6199,7 +7419,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); @@ -6209,16 +7431,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); } @@ -6239,6 +7485,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; @@ -6271,6 +7522,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; @@ -6508,6 +7762,13 @@ 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) @@ -7246,7 +8507,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); @@ -7286,7 +8547,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); } } @@ -7537,6 +8798,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) { @@ -7628,6 +8905,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); @@ -7705,6 +8994,28 @@ void DumpLevel(struct LevelInfo *level) 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); } @@ -7730,6 +9041,46 @@ void DumpLevels(void) CloseAllAndExit(0); } +void DumpLevelsetFromFilename_BD(char *filename) +{ + if (leveldir_current == NULL) // no levelsets loaded yet + bd_open_all(); + + if (!LoadNativeLevel_BD(filename, 0, FALSE)) + CloseAllAndExit(0); // function has already printed warning + + PrintLine("-", 79); + Print("Levelset '%s'\n", filename); + PrintLine("-", 79); + + DumpLevelset_BD(); + + PrintLine("-", 79); + + CloseAllAndExit(0); +} + +void DumpLevelset(void) +{ + static LevelDirTree *dumplevelset_leveldir = NULL; + + dumplevelset_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumplevelset_leveldir); + if (dumplevelset_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumplevelset_leveldir); + + PrintLine("-", 79); + Print("Levelset '%s'\n", dumplevelset_leveldir->identifier); + PrintLine("-", 79); + + Print("Number of levels: %d\n", dumplevelset_leveldir->levels); + Print("First level number: %d\n", dumplevelset_leveldir->first_level); + + PrintLine("-", 79); + + CloseAllAndExit(0); +} + // ============================================================================ // tape file functions @@ -7755,6 +9106,7 @@ static void setTapeInfoToDefaults(void) tape.level_nr = level_nr; tape.counter = 0; tape.changed = FALSE; + tape.solved = FALSE; tape.recording = FALSE; tape.playing = FALSE; @@ -7841,8 +9193,7 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) setTapeActionFlags(tape, getFile8Bit(file)); tape->property_bits = getFile8Bit(file); - - ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED); + tape->solved = getFile8Bit(file); engine_version = getFileVersion(file); if (engine_version > 0) @@ -7944,7 +9295,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]) { @@ -8234,10 +9585,29 @@ void LoadSolutionTape(int 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); + 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) @@ -8273,9 +9643,7 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) putFile8Bit(file, getTapeActionValue(tape)); putFile8Bit(file, tape->property_bits); - - // unused bytes not at the end here for 4-byte alignment of engine_version - WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED); + putFile8Bit(file, tape->solved); putFileVersion(file, tape->engine_version); } @@ -8401,7 +9769,7 @@ void SaveScoreTape(int nr) char *filename = getScoreTapeFilename(tape.score_tape_basename, nr); // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + InitScoreTapeDirectory(levelset.identifier, nr); SaveTapeExt(filename); } @@ -8457,6 +9825,10 @@ void DumpTape(struct TapeInfo *tape) 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]"); @@ -8565,6 +9937,13 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) 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; @@ -8573,7 +9952,15 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) 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) @@ -8720,6 +10107,18 @@ static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) 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; @@ -8821,6 +10220,7 @@ void LoadScore(int nr) { "INFO", -1, LoadScore_INFO }, { "NAME", -1, LoadScore_NAME }, { "SCOR", -1, LoadScore_SCOR }, + { "SC4R", -1, LoadScore_SC4R }, { "TIME", -1, LoadScore_TIME }, { "TAPE", -1, LoadScore_TAPE }, @@ -8840,870 +10240,343 @@ void LoadScore(int nr) 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_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 time_chunk_size; - int tape_chunk_size; - - 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; - time_chunk_size = scores.num_entries * 4; - tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; - - 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); - - 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); -} - -void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data, - char *error) -{ -#if defined(PLATFORM_EMSCRIPTEN) - // threads currently not fully supported by Emscripten/SDL and some browsers - function(data); -#else - SDL_Thread *thread = SDL_CreateThread(function, name, data); - - if (thread != NULL) - SDL_DetachThread(thread); - else - Error("Cannot create thread to %s!", error); - - // nasty kludge to lower probability of intermingled thread error messages - Delay(1); -#endif -} - -char *getPasswordJSON(char *password) -{ - static char password_json[MAX_FILENAME_LEN] = ""; - static boolean initialized = FALSE; - - if (!initialized) - { - if (password != NULL && - !strEqual(password, "") && - !strEqual(password, UNDEFINED_PASSWORD)) - snprintf(password_json, MAX_FILENAME_LEN, - " \"password\": \"%s\",\n", - setup.api_server_password); - - initialized = TRUE; - } - - return password_json; -} - -struct ApiGetScoreThreadData -{ - int level_nr; - char *score_cache_filename; -}; - -static void *CreateThreadData_ApiGetScore(int nr) -{ - struct ApiGetScoreThreadData *data = - checked_malloc(sizeof(struct ApiGetScoreThreadData)); - char *score_cache_filename = getScoreCacheFilename(nr); - - data->level_nr = nr; - data->score_cache_filename = getStringCopy(score_cache_filename); - - return data; -} - -static void FreeThreadData_ApiGetScore(void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - - checked_free(data->score_cache_filename); - checked_free(data); -} - -static boolean SetRequest_ApiGetScore(struct HttpRequest *request, - void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - int level_nr = data->level_nr; - - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_GET; - - char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); - char *levelset_name = getEscapedJSON(leveldir_current->name); - - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"levelset_name\": \"%s\",\n" - " \"level_nr\": \"%d\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - levelset_identifier, - levelset_name, - level_nr); + 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); - checked_free(levelset_identifier); - checked_free(levelset_name); + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this score chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &scores); - ConvertHttpRequestBodyToServerEncoding(request); + // 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); + } + } + } + } - return TRUE; + closeFile(file); } -static void HandleResponse_ApiGetScore(struct HttpResponse *response, - void *data_raw) +#if ENABLE_HISTORIC_CHUNKS +void SaveScore_OLD(int nr) { - struct ApiGetScoreThreadData *data = data_raw; - - if (response->body_size == 0) - { - // no scores available for this level - - return; - } - - ConvertHttpResponseBodyToClientEncoding(response); - - char *filename = data->score_cache_filename; - FILE *file; int i; + char *filename = getScoreFilename(nr); + FILE *file; // used instead of "leveldir_current->subdir" (for network games) - InitScoreCacheDirectory(levelset.identifier); + InitScoreDirectory(levelset.identifier); if (!(file = fopen(filename, MODE_WRITE))) { - Warn("cannot save score cache file '%s'", filename); + Warn("cannot save score for level %d", nr); return; } - for (i = 0; i < response->body_size; i++) - fputc(response->body[i], file); + 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); - - server_scores.updated = TRUE; -} - -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); - - if (response != NULL) - { - HandleResponse_ApiGetScore(response, data_raw); - - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } - - FreeThreadData_ApiGetScore(data_raw); } +#endif -static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) +static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores) { - Error("server failed to handle request: %d %s", code, status); - - FreeThreadData_ApiGetScore(data_raw); + putFileVersion(file, scores->file_version); + putFileVersion(file, scores->game_version); } -static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) +static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores) { - // nothing to do here -} + int level_identifier_size = strlen(scores->level_identifier) + 1; + int i; -static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiGetScore(request, data_raw)) - { - FreeThreadData_ApiGetScore(data_raw); + putFile16BitBE(file, level_identifier_size); - return; - } + for (i = 0; i < level_identifier_size; i++) + putFile8Bit(file, scores->level_identifier[i]); - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiGetScore_Loaded, - Emscripten_ApiGetScore_Failed, - Emscripten_ApiGetScore_Progress); + putFile16BitBE(file, scores->level_nr); + putFile16BitBE(file, scores->num_entries); } -#else - -static void ApiGetScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) +static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores) { - if (!SetRequest_ApiGetScore(request, data_raw)) - return; - - if (!DoHttpRequest(request, response)) - { - Error("HTTP request failed: %s", GetHttpError()); - - return; - } + int i, j; - if (!HTTP_SUCCESS(response->status_code)) + for (i = 0; i < scores->num_entries; i++) { - // do not show error message if no scores found for this level set - if (response->status_code == 404) - return; - - Error("server failed to handle request: %d %s", - response->status_code, - response->status_text); + int name_size = strlen(scores->entry[i].name); - return; + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0)); } - - HandleResponse_ApiGetScore(response, data_raw); } -static void ApiGetScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) +static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) { - ApiGetScore_HttpRequestExt(request, response, data_raw); + int i; - FreeThreadData_ApiGetScore(data_raw); + for (i = 0; i < scores->num_entries; i++) + putFile16BitBE(file, scores->entry[i].score); } -#endif -static int ApiGetScoreThread(void *data_raw) +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) { - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiGetScore_HttpRequest(request, data_raw); -#else - ApiGetScore_HttpRequest(request, response, data_raw); -#endif - - checked_free(request); - checked_free(response); + int i; - return 0; + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); } -static void ApiGetScoreAsThread(int nr) +static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) { - struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr); + int i; - ExecuteAsThread(ApiGetScoreThread, - "ApiGetScore", data, - "download scores from server"); + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].time); } -static void LoadServerScoreFromCache(int nr) +static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores) { - 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 }, - - { 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++) + for (i = 0; i < scores->num_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; - } + int size = strlen(scores->entry[i].tape_basename); - server_scores.entry[i] = score_entry; + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0)); } - - freeSetupFileHash(score_hash); } -void LoadServerScore(int nr, boolean download_score) +static void SaveScoreToFilename(char *filename) { - 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); + 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 (download_score && runtime.use_api_server) + if (!(file = fopen(filename, MODE_WRITE))) { - // 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); + Warn("cannot save score file '%s'", filename); + + return; } -} -static char *get_file_base64(char *filename) -{ - struct stat file_status; + 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; - if (stat(filename, &file_status) != 0) - { - Error("cannot stat file '%s'", filename); + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; - return NULL; - } + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); - int buffer_size = file_status.st_size; - byte *buffer = checked_malloc(buffer_size); - FILE *file; - int i; + putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE); + SaveScore_VERS(file, &scores); - if (!(file = fopen(filename, MODE_READ))) - { - Error("cannot open file '%s'", filename); + putFileChunkBE(file, "INFO", info_chunk_size); + SaveScore_INFO(file, &scores); - checked_free(buffer); + putFileChunkBE(file, "NAME", name_chunk_size); + SaveScore_NAME(file, &scores); - return NULL; + if (has_large_score_values) + { + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); } - - for (i = 0; i < buffer_size; i++) + else { - int c = fgetc(file); - - if (c == EOF) - { - Error("cannot read from input file '%s'", filename); - - fclose(file); - checked_free(buffer); - - return NULL; - } - - buffer[i] = (byte)c; + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); } - fclose(file); - - int buffer_encoded_size = base64_encoded_size(buffer_size); - char *buffer_encoded = checked_malloc(buffer_encoded_size); + putFileChunkBE(file, "TIME", time_chunk_size); + SaveScore_TIME(file, &scores); - base64_encode(buffer_encoded, buffer, buffer_size); + putFileChunkBE(file, "TAPE", tape_chunk_size); + SaveScore_TAPE(file, &scores); - checked_free(buffer); + fclose(file); - return buffer_encoded; + SetFilePermissions(filename, PERMS_PRIVATE); } -struct ApiAddScoreThreadData +void SaveScore(int nr) { - int level_nr; - boolean tape_saved; - char *score_tape_filename; - struct ScoreEntry score_entry; -}; + char *filename = getScoreFilename(nr); + int i; -static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved, - char *score_tape_filename) -{ - struct ApiAddScoreThreadData *data = - checked_malloc(sizeof(struct ApiAddScoreThreadData)); - struct ScoreEntry *score_entry = &scores.entry[scores.last_added]; + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + scores.file_version = FILE_VERSION_ACTUAL; + scores.game_version = GAME_VERSION_ACTUAL; - if (score_tape_filename == NULL) - score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr); + strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN); + scores.level_identifier[MAX_FILENAME_LEN] = '\0'; + scores.level_nr = level_nr; - data->level_nr = nr; - data->tape_saved = tape_saved; - data->score_entry = *score_entry; - data->score_tape_filename = getStringCopy(score_tape_filename); + 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; - return data; -} + scores.num_entries = i; -static void FreeThreadData_ApiAddScore(void *data_raw) -{ - struct ApiAddScoreThreadData *data = data_raw; + if (scores.num_entries == 0) + return; - checked_free(data->score_tape_filename); - checked_free(data); + SaveScoreToFilename(filename); } -static boolean SetRequest_ApiAddScore(struct HttpRequest *request, - void *data_raw) +static void LoadServerScoreFromCache(int nr) { - struct ApiAddScoreThreadData *data = data_raw; - struct ScoreEntry *score_entry = &data->score_entry; - char *score_tape_filename = data->score_tape_filename; - boolean tape_saved = data->tape_saved; - int level_nr = data->level_nr; - - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_ADD; - - char *tape_base64 = get_file_base64(score_tape_filename); - - if (tape_base64 == NULL) + struct ScoreEntry score_entry; + struct { - Error("loading and base64 encoding score tape file failed"); - - return FALSE; + 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 }, - char *player_name_raw = score_entry->name; - char *player_uuid_raw = setup.player_uuid; - - if (options.player_name != NULL && global.autoplay_leveldir != NULL) - { - player_name_raw = options.player_name; - player_uuid_raw = ""; - } - - char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); - char *levelset_name = getEscapedJSON(leveldir_current->name); - char *levelset_author = getEscapedJSON(leveldir_current->author); - char *level_name = getEscapedJSON(level.name); - char *level_author = getEscapedJSON(level.author); - char *player_name = getEscapedJSON(player_name_raw); - char *player_uuid = getEscapedJSON(player_uuid_raw); - - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"batch_time\": \"%d\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"levelset_name\": \"%s\",\n" - " \"levelset_author\": \"%s\",\n" - " \"levelset_num_levels\": \"%d\",\n" - " \"levelset_first_level\": \"%d\",\n" - " \"level_nr\": \"%d\",\n" - " \"level_name\": \"%s\",\n" - " \"level_author\": \"%s\",\n" - " \"rate_time_over_score\": \"%d\",\n" - " \"player_name\": \"%s\",\n" - " \"player_uuid\": \"%s\",\n" - " \"score\": \"%d\",\n" - " \"time\": \"%d\",\n" - " \"tape_basename\": \"%s\",\n" - " \"tape_saved\": \"%d\",\n" - " \"tape\": \"%s\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - (int)global.autoplay_time, - levelset_identifier, - levelset_name, - levelset_author, - leveldir_current->levels, - leveldir_current->first_level, - level_nr, - level_name, - level_author, - level.rate_time_over_score, - player_name, - player_uuid, - score_entry->score, - score_entry->time, - score_entry->tape_basename, - tape_saved, - tape_base64); - - checked_free(tape_base64); - - checked_free(levelset_identifier); - checked_free(levelset_name); - checked_free(levelset_author); - checked_free(level_name); - checked_free(level_author); - checked_free(player_name); - checked_free(player_uuid); - - ConvertHttpRequestBodyToServerEncoding(request); - - return TRUE; -} + { NULL, FALSE, 0 } + }; + char *filename = getScoreCacheFilename(nr); + SetupFileHash *score_hash = loadSetupFileHash(filename); + int i, j; -static void HandleResponse_ApiAddScore(struct HttpResponse *response, - void *data_raw) -{ - server_scores.uploaded = TRUE; -} + server_scores.num_entries = 0; -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + if (score_hash == NULL) + return; - if (response != NULL) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - HandleResponse_ApiAddScore(response, data_raw); + score_entry = server_scores.entry[i]; - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } + for (j = 0; score_mapping[j].value != NULL; j++) + { + char token[10]; - FreeThreadData_ApiAddScore(data_raw); -} + sprintf(token, "%02d.%d", i, j); -static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); + char *value = getHashEntry(score_hash, token); - FreeThreadData_ApiAddScore(data_raw); -} + if (value == NULL) + continue; -static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here -} + if (score_mapping[j].is_string) + { + char *score_value = (char *)score_mapping[j].value; + int value_size = score_mapping[j].string_size; -static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiAddScore(request, data_raw)) - { - FreeThreadData_ApiAddScore(data_raw); + strncpy(score_value, value, value_size); + score_value[value_size] = '\0'; + } + else + { + int *score_value = (int *)score_mapping[j].value; - return; + *score_value = atoi(value); + } + + server_scores.num_entries = i + 1; + } + + server_scores.entry[i] = score_entry; } - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiAddScore_Loaded, - Emscripten_ApiAddScore_Failed, - Emscripten_ApiAddScore_Progress); + freeSetupFileHash(score_hash); } -#else - -static void ApiAddScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) +void LoadServerScore(int nr, boolean download_score) { - if (!SetRequest_ApiAddScore(request, data_raw)) + if (!setup.use_api_server) return; - if (!DoHttpRequest(request, response)) - { - Error("HTTP request failed: %s", GetHttpError()); + // always start with reliable default values + setServerScoreInfoToDefaults(); - return; - } + // 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 (!HTTP_SUCCESS(response->status_code)) + if (download_score && runtime.use_api_server) { - Error("server failed to handle request: %d %s", - response->status_code, - response->status_text); - - return; + // 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); } - - HandleResponse_ApiAddScore(response, data_raw); -} - -static void ApiAddScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiAddScore_HttpRequestExt(request, response, data_raw); - - FreeThreadData_ApiAddScore(data_raw); } -#endif -static int ApiAddScoreThread(void *data_raw) +void PrepareScoreTapesForUpload(char *leveldir_subdir) { - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiAddScore_HttpRequest(request, data_raw); -#else - ApiAddScore_HttpRequest(request, response, data_raw); -#endif - - checked_free(request); - checked_free(response); + MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); - return 0; -} + // if score tape not uploaded, ask for uploading missing tapes later + if (!setup.has_remaining_tapes) + setup.ask_for_remaining_tapes = TRUE; -static void ApiAddScoreAsThread(int nr, boolean tape_saved, - char *score_tape_filename) -{ - struct ApiAddScoreThreadData *data = - CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename); + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; - ExecuteAsThread(ApiAddScoreThread, - "ApiAddScore", data, - "upload score to server"); + SaveSetup_ServerSetup(); } void SaveServerScore(int nr, boolean tape_saved) { if (!runtime.use_api_server) + { + PrepareScoreTapesForUpload(leveldir_current->subdir); + return; + } ApiAddScoreAsThread(nr, tape_saved, NULL); } @@ -9720,6 +10593,7 @@ void SaveServerScoreFromFile(int nr, boolean tape_saved, void LoadLocalAndServerScore(int nr, boolean download_score) { int last_added_local = scores.last_added_local; + boolean force_last_added = scores.force_last_added; // needed if only showing server scores setScoreInfoToDefaults(); @@ -9739,6 +10613,9 @@ void LoadLocalAndServerScore(int nr, boolean download_score) // merge local scores with scores from server MergeServerScore(); } + + if (force_last_added) + scores.force_last_added = force_last_added; } @@ -9779,6 +10656,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.toons, "toons" }, + { + TYPE_SWITCH, + &setup.global_animations, "global_animations" + }, { TYPE_SWITCH, &setup.scroll_delay, "scroll_delay" @@ -9807,6 +10688,14 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.autorecord, "automatic_tape_recording" }, + { + TYPE_SWITCH, + &setup.autorecord_after_replay, "autorecord_after_replay" + }, + { + TYPE_SWITCH, + &setup.auto_pause_on_start, "auto_pause_on_start" + }, { TYPE_SWITCH, &setup.show_titlescreen, "show_titlescreen" @@ -9827,6 +10716,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.skip_levels, "skip_levels" }, + { + TYPE_SWITCH_3_STATES, + &setup.allow_skipping_levels, "allow_skipping_levels" + }, { TYPE_SWITCH, &setup.increment_levels, "increment_levels" @@ -9915,6 +10808,62 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.game_frame_delay, "game_frame_delay" }, + { + TYPE_INTEGER, + &setup.default_game_engine_type, "default_game_engine_type" + }, + { + TYPE_SWITCH, + &setup.bd_skip_uncovering, "bd_skip_uncovering" + }, + { + TYPE_SWITCH, + &setup.bd_skip_hatching, "bd_skip_hatching" + }, + { + TYPE_SWITCH, + &setup.bd_scroll_delay, "bd_scroll_delay" + }, + { + TYPE_SWITCH, + &setup.bd_show_invisible_outbox, "bd_show_invisible_outbox" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_smooth_movements, "bd_smooth_movements" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_pushing_graphics, "bd_pushing_graphics" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_up_down_graphics, "bd_up_down_graphics" + }, + { + TYPE_SWITCH_3_STATES, + &setup.bd_skip_falling_sounds, "bd_skip_falling_sounds" + }, + { + TYPE_INTEGER, + &setup.bd_palette_c64, "bd_palette_c64" + }, + { + TYPE_INTEGER, + &setup.bd_palette_c64dtv, "bd_palette_c64dtv" + }, + { + TYPE_INTEGER, + &setup.bd_palette_atari, "bd_palette_atari" + }, + { + TYPE_INTEGER, + &setup.bd_default_color_type, "bd_default_color_type" + }, + { + TYPE_SWITCH, + &setup.bd_random_colors, "bd_random_colors" + }, { TYPE_SWITCH, &setup.sp_show_border_elements, "sp_show_border_elements" @@ -9948,15 +10897,15 @@ static struct TokenInfo global_setup_tokens[] = &setup.music_set, "music_set" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_graphics, "override_level_graphics" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_sounds, "override_level_sounds" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.override_level_music, "override_level_music" }, { @@ -9971,6 +10920,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.volume_music, "volume_music" }, + { + TYPE_SWITCH, + &setup.audio_sample_rate_44100, "audio_sample_rate_44100" + }, { TYPE_SWITCH, &setup.network_mode, "network_mode" @@ -10023,6 +10976,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.touch.grid_ysize[1], "touch.virtual_buttons.1.ysize" }, + { + TYPE_SWITCH, + &setup.touch.overlay_buttons, "touch.overlay_buttons" + }, }; static struct TokenInfo auto_setup_tokens[] = @@ -10039,6 +10996,10 @@ 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" @@ -10055,10 +11016,22 @@ static struct TokenInfo server_setup_tokens[] = 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[] = @@ -10099,6 +11072,14 @@ static struct TokenInfo editor_cascade_setup_tokens[] = TYPE_SWITCH, &setup.editor_cascade.el_bd, "editor.cascade.el_bd" }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bdx, "editor.cascade.el_bdx" + }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_bdx_effects, "editor.cascade.el_bdx_effects" + }, { TYPE_SWITCH, &setup.editor_cascade.el_em, "editor.cascade.el_em" @@ -10151,6 +11132,10 @@ static struct TokenInfo editor_cascade_setup_tokens[] = 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" @@ -10175,6 +11160,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.load_game, "shortcut.load_game" }, + { + TYPE_KEY_X11, + &setup.shortcut.restart_game, "shortcut.restart_game" + }, + { + TYPE_KEY_X11, + &setup.shortcut.pause_before_end, "shortcut.pause_before_end" + }, { TYPE_KEY_X11, &setup.shortcut.toggle_pause, "shortcut.toggle_pause" @@ -10251,6 +11244,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.snap_down, "shortcut.snap_down" }, + { + TYPE_KEY_X11, + &setup.shortcut.speed_fast, "shortcut.speed_fast" + }, + { + TYPE_KEY_X11, + &setup.shortcut.speed_slow, "shortcut.speed_slow" + }, }; static struct SetupInputInfo setup_input; @@ -10414,55 +11415,119 @@ static struct TokenInfo internal_setup_tokens[] = }, { TYPE_BOOLEAN, - &setup.internal.choose_from_top_leveldir, "choose_from_top_leveldir" + &setup.internal.choose_from_top_leveldir, "choose_from_top_leveldir" + }, + { + TYPE_BOOLEAN, + &setup.internal.show_scaling_in_title, "show_scaling_in_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.create_user_levelset, "create_user_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_screens_from_main, "info_screens_from_main" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_game, "menu_game" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_engines, "menu_engines" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_editor, "menu_editor" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_graphics, "menu_graphics" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_sound, "menu_sound" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_artwork, "menu_artwork" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_input, "menu_input" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_touch, "menu_touch" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts, "menu_shortcuts" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_exit, "menu_exit" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_save_and_exit, "menu_save_and_exit" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_various, "menu_shortcuts_various" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_focus, "menu_shortcuts_focus" }, { TYPE_BOOLEAN, - &setup.internal.show_scaling_in_title, "show_scaling_in_title" + &setup.internal.menu_shortcuts_tape, "menu_shortcuts_tape" }, { TYPE_BOOLEAN, - &setup.internal.create_user_levelset, "create_user_levelset" + &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound" }, { TYPE_BOOLEAN, - &setup.internal.menu_game, "menu_game" + &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap" }, { TYPE_BOOLEAN, - &setup.internal.menu_editor, "menu_editor" + &setup.internal.menu_shortcuts_speed, "menu_shortcuts_speed" }, { TYPE_BOOLEAN, - &setup.internal.menu_graphics, "menu_graphics" + &setup.internal.info_title, "info_title" }, { TYPE_BOOLEAN, - &setup.internal.menu_sound, "menu_sound" + &setup.internal.info_elements, "info_elements" }, { TYPE_BOOLEAN, - &setup.internal.menu_artwork, "menu_artwork" + &setup.internal.info_music, "info_music" }, { TYPE_BOOLEAN, - &setup.internal.menu_input, "menu_input" + &setup.internal.info_credits, "info_credits" }, { TYPE_BOOLEAN, - &setup.internal.menu_touch, "menu_touch" + &setup.internal.info_program, "info_program" }, { TYPE_BOOLEAN, - &setup.internal.menu_shortcuts, "menu_shortcuts" + &setup.internal.info_version, "info_version" }, { TYPE_BOOLEAN, - &setup.internal.menu_exit, "menu_exit" + &setup.internal.info_levelset, "info_levelset" }, { TYPE_BOOLEAN, - &setup.internal.menu_save_and_exit, "menu_save_and_exit" + &setup.internal.info_exit, "info_exit" }, }; @@ -10560,7 +11625,7 @@ static struct TokenInfo debug_setup_tokens[] = &setup.debug.show_frames_per_second, "debug.show_frames_per_second" }, { - TYPE_SWITCH3, + TYPE_SWITCH_3_STATES, &setup.debug.xsn_mode, "debug.xsn_mode" }, { @@ -10575,6 +11640,14 @@ static struct TokenInfo options_setup_tokens[] = TYPE_BOOLEAN, &setup.options.verbose, "options.verbose" }, + { + TYPE_BOOLEAN, + &setup.options.debug, "options.debug" + }, + { + TYPE_STRING, + &setup.options.debug_mode, "options.debug_mode" + }, }; static void setSetupInfoToDefaults(struct SetupInfo *si) @@ -10590,6 +11663,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sound_music = TRUE; si->sound_simple = TRUE; si->toons = TRUE; + si->global_animations = TRUE; si->scroll_delay = TRUE; si->forced_scroll_delay = FALSE; si->scroll_delay_value = STD_SCROLL_DELAY; @@ -10597,11 +11671,14 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; si->fade_screens = TRUE; si->autorecord = TRUE; + si->autorecord_after_replay = TRUE; + si->auto_pause_on_start = FALSE; si->show_titlescreen = TRUE; si->quick_doors = FALSE; si->team_mode = FALSE; si->handicap = TRUE; si->skip_levels = TRUE; + si->allow_skipping_levels = STATE_ASK; si->increment_levels = TRUE; si->auto_play_next_level = TRUE; si->count_score_after_game = TRUE; @@ -10624,6 +11701,20 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->prefer_extra_panel_items = TRUE; si->game_speed_extended = FALSE; si->game_frame_delay = GAME_FRAME_DELAY; + si->default_game_engine_type = GAME_ENGINE_TYPE_RND; + si->bd_skip_uncovering = FALSE; + si->bd_skip_hatching = FALSE; + si->bd_scroll_delay = TRUE; + si->bd_show_invisible_outbox = FALSE; + si->bd_smooth_movements = STATE_TRUE; + si->bd_pushing_graphics = STATE_TRUE; + si->bd_up_down_graphics = STATE_TRUE; + si->bd_skip_falling_sounds = STATE_TRUE; + 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; @@ -10634,13 +11725,14 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); si->music_set = getStringCopy(MUS_CLASSIC_SUBDIR); - si->override_level_graphics = FALSE; - si->override_level_sounds = FALSE; - si->override_level_music = FALSE; + si->override_level_graphics = STATE_FALSE; + si->override_level_sounds = STATE_FALSE; + si->override_level_music = STATE_FALSE; si->volume_simple = 100; // percent si->volume_loops = 100; // percent si->volume_music = 100; // percent + si->audio_sample_rate_44100 = FALSE; si->network_mode = FALSE; si->network_player_nr = 0; // first player @@ -10702,7 +11794,11 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->touch.grid_initialized = video.initialized; + si->touch.overlay_buttons = FALSE; + si->editor.el_boulderdash = TRUE; + si->editor.el_boulderdash_native = TRUE; + si->editor.el_boulderdash_effects = TRUE; si->editor.el_emerald_mine = TRUE; si->editor.el_emerald_mine_club = TRUE; si->editor.el_more = TRUE; @@ -10733,6 +11829,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; + si->shortcut.restart_game = DEFAULT_KEY_RESTART_GAME; + si->shortcut.pause_before_end = DEFAULT_KEY_PAUSE_BEFORE_END; si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; @@ -10757,10 +11855,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.snap_up = DEFAULT_KEY_SNAP_UP; si->shortcut.snap_down = DEFAULT_KEY_SNAP_DOWN; + si->shortcut.speed_fast = DEFAULT_KEY_SPEED_FAST; + si->shortcut.speed_slow = DEFAULT_KEY_SPEED_SLOW; + for (i = 0; i < MAX_PLAYERS; i++) { si->input[i].use_joystick = FALSE; - si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i)); + si->input[i].joy.device_name = getStringCopy(getDeviceNameFromJoystickNr(i)); si->input[i].joy.xleft = JOYSTICK_XLEFT; si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE; si->input[i].joy.xright = JOYSTICK_XRIGHT; @@ -10804,6 +11905,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->internal.choose_from_top_leveldir = FALSE; si->internal.show_scaling_in_title = TRUE; si->internal.create_user_levelset = TRUE; + si->internal.info_screens_from_main = FALSE; si->internal.default_window_width = WIN_XSIZE_DEFAULT; si->internal.default_window_height = WIN_YSIZE_DEFAULT; @@ -10835,13 +11937,16 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->debug.show_frames_per_second = FALSE; - si->debug.xsn_mode = AUTO; + si->debug.xsn_mode = STATE_AUTO; si->debug.xsn_percent = 0; si->options.verbose = FALSE; + si->options.debug = FALSE; + si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING); #if defined(PLATFORM_ANDROID) si->fullscreen = TRUE; + si->touch.overlay_buttons = TRUE; #endif setHideSetupEntry(&setup.debug.xsn_mode); @@ -10855,17 +11960,23 @@ static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si) { si->player_uuid = NULL; // (will be set later) + si->player_version = 1; // (will be set later) 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; } static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) { si->editor_cascade.el_bd = TRUE; + si->editor_cascade.el_bdx = TRUE; + si->editor_cascade.el_bdx_effects = FALSE; si->editor_cascade.el_em = TRUE; si->editor_cascade.el_emc = TRUE; si->editor_cascade.el_rnd = TRUE; @@ -10881,6 +11992,7 @@ static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) 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; @@ -11160,6 +12272,12 @@ void LoadSetup_Default(void) // try to load setup values from default setup file filename = getDefaultSetupFilename(); + if (fileExists(filename)) + LoadSetupFromFilename(filename); + + // try to load setup values from platform setup file + filename = getPlatformSetupFilename(); + if (fileExists(filename)) LoadSetupFromFilename(filename); @@ -11214,6 +12332,7 @@ void LoadSetup_ServerSetup(void) { // player UUID does not yet exist in setup file setup.player_uuid = getStringCopy(getUUID()); + setup.player_version = 2; SaveSetup_ServerSetup(); } @@ -11386,7 +12505,7 @@ void SaveSetup_Default(void) fprintf(file, "\n"); for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++) if (!strPrefix(debug_setup_tokens[i].text, "debug.xsn_") || - setup.debug.xsn_mode != AUTO) + setup.debug.xsn_mode != STATE_AUTO) fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); fprintf(file, "\n"); @@ -11686,8 +12805,9 @@ static boolean string_has_parameter(char *s, char *s_contained) char next_char = s[strlen(s_contained)]; // check if next character is delimiter or whitespace - return (next_char == ',' || next_char == '\0' || - next_char == ' ' || next_char == '\t' ? TRUE : FALSE); + if (next_char == ',' || next_char == '\0' || + next_char == ' ' || next_char == '\t') + return TRUE; } // check if string contains another parameter string after a comma @@ -11705,6 +12825,85 @@ static boolean string_has_parameter(char *s, char *s_contained) return string_has_parameter(substring, s_contained); } +static int get_anim_parameter_value_ce(char *s) +{ + char *s_ptr = s; + char *pattern_1 = "ce_change:custom_"; + char *pattern_2 = ".page_"; + int pattern_1_len = strlen(pattern_1); + char *matching_char = strstr(s_ptr, pattern_1); + int result = ANIM_EVENT_NONE; + + if (matching_char == NULL) + return ANIM_EVENT_NONE; + + result = ANIM_EVENT_CE_CHANGE; + + s_ptr = matching_char + pattern_1_len; + + // check for custom element number ("custom_X", "custom_XX" or "custom_XXX") + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_ce_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + } + + if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS) + return ANIM_EVENT_NONE; + + // custom element stored as 0 to 255 + gic_ce_nr--; + + result |= gic_ce_nr << ANIM_EVENT_CE_BIT; + } + else + { + // invalid custom element number specified + + return ANIM_EVENT_NONE; + } + + // check for change page number ("page_X" or "page_XX") (optional) + if (strPrefix(s_ptr, pattern_2)) + { + s_ptr += strlen(pattern_2); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_page_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0'); + + if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES) + return ANIM_EVENT_NONE; + + // change page stored as 1 to 32 (0 means "all change pages") + + result |= gic_page_nr << ANIM_EVENT_PAGE_BIT; + } + else + { + // invalid animation part number specified + + return ANIM_EVENT_NONE; + } + } + + // discard result if next character is neither delimiter nor whitespace + if (!(*s_ptr == ',' || *s_ptr == '\0' || + *s_ptr == ' ' || *s_ptr == '\t')) + return ANIM_EVENT_NONE; + + return result; +} + static int get_anim_parameter_value(char *s) { int event_value[] = @@ -11730,6 +12929,11 @@ static int get_anim_parameter_value(char *s) int result = ANIM_EVENT_NONE; int i; + result = get_anim_parameter_value_ce(s); + + if (result != ANIM_EVENT_NONE) + return result; + for (i = 0; i < ARRAY_SIZE(event_value); i++) { matching_char = strstr(s_ptr, pattern_1[i]); @@ -11859,6 +13063,18 @@ static int get_anim_action_parameter_value(char *token) result = -(int)key; } + if (result == -1) + { + if (isURL(token)) + { + result = get_hash_from_string(token); // unsigned int => int + result = ABS(result); // may be negative now + result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0); + + setHashEntry(anim_url_hash, int2str(result, 0), token); + } + } + if (result == -1) result = ANIM_EVENT_ACTION_NONE; @@ -11887,6 +13103,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type) strEqual(value, "lower") ? POS_LOWER : strEqual(value, "bottom") ? POS_BOTTOM : strEqual(value, "any") ? POS_ANY : + strEqual(value, "ce") ? POS_CE : + strEqual(value, "ce_trigger") ? POS_CE_TRIGGER : strEqual(value, "last") ? POS_LAST : POS_UNDEFINED); } else if (strEqual(suffix, ".align")) @@ -11911,6 +13129,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) 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 : @@ -11918,6 +13137,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type) 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 (string_has_parameter(value, "once")) @@ -11948,7 +13169,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) else if (strEqual(suffix, ".class")) { result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE : - get_hash_from_key(value)); + get_hash_from_string(value)); } else if (strEqual(suffix, ".style")) { @@ -11974,11 +13195,16 @@ int get_parameter_value(char *value_raw, char *suffix, int type) if (string_has_parameter(value, "multiple_actions")) result |= STYLE_MULTIPLE_ACTIONS; + + if (string_has_parameter(value, "consume_ce_event")) + result |= STYLE_CONSUME_CE_EVENT; } else if (strEqual(suffix, ".fade_mode")) { result = (string_has_parameter(value, "none") ? FADE_MODE_NONE : string_has_parameter(value, "fade") ? FADE_MODE_FADE : + string_has_parameter(value, "fade_in") ? FADE_MODE_FADE_IN : + string_has_parameter(value, "fade_out") ? FADE_MODE_FADE_OUT : string_has_parameter(value, "crossfade") ? FADE_MODE_CROSSFADE : string_has_parameter(value, "melt") ? FADE_MODE_MELT : string_has_parameter(value, "curtain") ? FADE_MODE_CURTAIN : @@ -12025,14 +13251,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 = @@ -12040,6 +13270,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; @@ -12245,7 +13481,7 @@ static void InitMenuDesignSettings_SpecialPostProcessing(void) vp_playfield->width = MIN(vp_playfield->width, vp_playfield->max_width); if (vp_playfield->max_height != -1) - vp_playfield->height = MIN(vp_playfield->height,vp_playfield->max_height); + vp_playfield->height = MIN(vp_playfield->height, vp_playfield->max_height); // adjust playfield position according to specified alignment @@ -12480,6 +13716,45 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void) } } +static void InitMenuDesignSettings_PreviewPlayers_Ext(SetupFileHash *hash, + boolean initialize) +{ + // special case: check if network and preview player positions are redefined, + // to compare this later against the main menu level preview being redefined + struct TokenIntPtrInfo menu_config_players[] = + { + { "main.network_players.x", &menu.main.network_players.redefined }, + { "main.network_players.y", &menu.main.network_players.redefined }, + { "main.preview_players.x", &menu.main.preview_players.redefined }, + { "main.preview_players.y", &menu.main.preview_players.redefined }, + { "preview.x", &preview.redefined }, + { "preview.y", &preview.redefined } + }; + int i; + + if (initialize) + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + *menu_config_players[i].value = FALSE; + } + else + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + if (getHashEntry(hash, menu_config_players[i].token) != NULL) + *menu_config_players[i].value = TRUE; + } +} + +static void InitMenuDesignSettings_PreviewPlayers(void) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(NULL, TRUE); +} + +static void InitMenuDesignSettings_PreviewPlayers_FromHash(SetupFileHash *hash) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(hash, FALSE); +} + static void LoadMenuDesignSettingsFromFilename(char *filename) { static struct TitleFadingInfo tfi; @@ -12614,7 +13889,9 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { { "menu.draw_xoffset.INFO", &menu.draw_xoffset_info[i] }, { "menu.draw_yoffset.INFO", &menu.draw_yoffset_info[i] }, - { "menu.list_size.INFO", &menu.list_size_info[i] } + { "menu.list_size.INFO", &menu.list_size_info[i] }, + { "menu.list_entry_size.INFO", &menu.list_entry_size_info[i] }, + { "menu.tile_size.INFO", &menu.tile_size_info[i] } }; for (j = 0; j < ARRAY_SIZE(menu_config); j++) @@ -12654,6 +13931,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) struct TokenIntPtrInfo menu_config[] = { { "menu.left_spacing.INFO", &menu.left_spacing_info[i] }, + { "menu.middle_spacing.INFO", &menu.middle_spacing_info[i] }, { "menu.right_spacing.INFO", &menu.right_spacing_info[i] }, { "menu.top_spacing.INFO", &menu.top_spacing_info[i] }, { "menu.bottom_spacing.INFO", &menu.bottom_spacing_info[i] }, @@ -12818,35 +14096,11 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) } } - // special case: check if network and preview player positions are redefined, - // to compare this later against the main menu level preview being redefined - struct TokenIntPtrInfo menu_config_players[] = - { - { "main.network_players.x", &menu.main.network_players.redefined }, - { "main.network_players.y", &menu.main.network_players.redefined }, - { "main.preview_players.x", &menu.main.preview_players.redefined }, - { "main.preview_players.y", &menu.main.preview_players.redefined }, - { "preview.x", &preview.redefined }, - { "preview.y", &preview.redefined } - }; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - *menu_config_players[i].value = FALSE; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - if (getHashEntry(setup_file_hash, menu_config_players[i].token) != NULL) - *menu_config_players[i].value = TRUE; - // read (and overwrite with) values that may be specified in config file - for (i = 0; image_config_vars[i].token != NULL; i++) - { - char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + InitMenuDesignSettings_FromHash(setup_file_hash, TRUE); - // (ignore definitions set to "[DEFAULT]" which are already initialized) - if (value != NULL && !strEqual(value, ARG_DEFAULT)) - *image_config_vars[i].value = - get_token_parameter_value(image_config_vars[i].token, value); - } + // special case: check if network and preview player positions are redefined + InitMenuDesignSettings_PreviewPlayers_FromHash(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -12857,6 +14111,7 @@ void LoadMenuDesignSettings(void) InitMenuDesignSettings_Static(); InitMenuDesignSettings_SpecialPreProcessing(); + InitMenuDesignSettings_PreviewPlayers(); if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS)) { @@ -12880,6 +14135,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(); @@ -12979,11 +14293,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 }, }; @@ -13079,14 +14395,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) @@ -13099,11 +14413,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); @@ -13112,76 +14428,68 @@ void LoadMusicInfo(void) new = &music_file_info; - for (i = 0; i < num_music; i++) + // get (configured or unconfigured) music file info for all levels + for (i = leveldir_current->first_level; + i <= leveldir_current->last_level; i++) { - music = getMusicListEntry(i); + int music_nr; - if (music->filename == NULL) - continue; + if (levelset.music[i] != MUS_UNDEFINED) + { + // get music file info for configured level music + music_nr = levelset.music[i]; + } + else if (num_music_noconf > 0) + { + // get music file info for unconfigured level music + int level_pos = i - leveldir_current->first_level; - if (strEqual(music->filename, UNDEFINED_FILENAME)) + music_nr = MAP_NOCONF_MUSIC(level_pos % num_music_noconf); + } + else + { continue; + } - // a configured file may be not recognized as music - if (!FileIsMusic(music->filename)) + char *basename = getMusicInfoEntryFilename(music_nr); + + if (basename == NULL) continue; - if (!music_info_listed(music_file_info, music->filename)) + if (!music_info_listed(music_file_info, basename)) { - *new = get_music_file_info(music->filename, i); + *new = get_music_file_info(basename, music_nr); if (*new != NULL) new = &(*new)->next; } } - if ((dir = openDirectory(music_directory)) == NULL) - { - Warn("cannot read music directory '%s'", music_directory); - - return; - } - - while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries + // get music file info for all remaining configured music files + for (i = 0; i < num_music; i++) { - char *basename = dir_entry->basename; - boolean music_already_used = FALSE; - int i; - - // skip all music files that are configured in music config file - for (i = 0; i < num_music; i++) - { - music = getMusicListEntry(i); - - if (music->filename == NULL) - continue; + music = getMusicListEntry(i); - if (strEqual(basename, music->filename)) - { - music_already_used = TRUE; - break; - } - } + if (music->filename == NULL) + continue; - if (music_already_used) + if (strEqual(music->filename, UNDEFINED_FILENAME)) continue; - if (!FileIsMusic(dir_entry->filename)) + // a configured file may be not recognized as music + if (!FileIsMusic(music->filename)) continue; - if (!music_info_listed(music_file_info, basename)) + if (!music_info_listed(music_file_info, music->filename)) { - *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf)); + *new = get_music_file_info(music->filename, i); if (*new != NULL) new = &(*new)->next; } - - num_music_noconf++; } - closeDirectory(dir); - + // get sound file info for all configured sound files for (i = 0; i < num_sounds; i++) { sound = getSoundListEntry(i); @@ -13203,6 +14511,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, @@ -13705,8 +15025,25 @@ void CreateCollectElementImages(void) int dst_width = anim_width * 2; int dst_height = anim_height * num_collect_images / 2; Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH); - char *basename = "RocksCollect.bmp"; - char *filename = getPath2(global.create_collect_images_dir, basename); + char *basename_bmp = "RocksCollect.bmp"; + char *basename_png = "RocksCollect.png"; + char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp); + char *filename_png = getPath2(global.create_collect_images_dir, basename_png); + int len_filename_bmp = strlen(filename_bmp); + int len_filename_png = strlen(filename_png); + int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png; + char cmd_convert[max_command_len]; + + snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"", + filename_bmp, + filename_png); + + // force using RGBA surface for destination bitmap + SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00)); + + dst_bitmap->surface = + SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); for (i = 0; i < MAX_NUM_ELEMENTS; i++) { @@ -13728,6 +15065,13 @@ void CreateCollectElementImages(void) BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y, tile_size, tile_size, 0, 0); + // force using RGBA surface for temporary bitmap (using transparent black) + SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00)); + + tmp_bitmap->surface = + SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + tmp_bitmap->surface_masked = tmp_bitmap->surface; for (j = 0; j < anim_frames; j++) @@ -13748,9 +15092,9 @@ void CreateCollectElementImages(void) frame_bitmap = half_bitmap; } - BlitBitmap(frame_bitmap, dst_bitmap, 0, 0, - frame_size_final, frame_size_final, - dst_x + j * tile_size + offset, dst_y + offset); + BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0, + frame_size_final, frame_size_final, + dst_x + j * tile_size + offset, dst_y + offset); FreeBitmap(frame_bitmap); } @@ -13762,11 +15106,18 @@ void CreateCollectElementImages(void) pos_collect_images++; } - if (SDL_SaveBMP(dst_bitmap->surface, filename) != 0) - Fail("cannot save element collecting image file '%s'", filename); + if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0) + Fail("cannot save element collecting image file '%s'", filename_bmp); FreeBitmap(dst_bitmap); + Info("Converting image file from BMP to PNG ..."); + + if (system(cmd_convert) != 0) + Fail("converting image file failed"); + + unlink(filename_bmp); + Info("Done."); CloseAllAndExit(0);