X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=a4f7f10a786f31803e77ae160a858778a96e86c9;hb=881bd7909d3a28c07c0bed2185e60a437b407e7c;hp=cfba947d0c50ac26aa6870d5d87cbb1f48f45d3d;hpb=b20de5f2d7dd54c56af6bda51748d2c03446bcdd;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index cfba947d..a4f7f10a 100644 --- a/src/files.c +++ b/src/files.c @@ -4,7 +4,7 @@ // (c) 1995-2014 by Artsoft Entertainment // Holger Schemel // info@artsoft.org -// http://www.artsoft.org/ +// https://www.artsoft.org/ // ---------------------------------------------------------------------------- // files.c // ============================================================================ @@ -58,7 +58,10 @@ #define TAPE_CHUNK_VERS_SIZE 8 // size of file version chunk #define TAPE_CHUNK_HEAD_SIZE 20 // size of tape file header -#define TAPE_CHUNK_HEAD_UNUSED 2 // unused tape header bytes +#define TAPE_CHUNK_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 #define LEVEL_CHUNK_CNT3_SIZE(x) (LEVEL_CHUNK_CNT3_HEADER + (x)) #define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE) @@ -67,7 +70,7 @@ // file identifier strings #define LEVEL_COOKIE_TMPL "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x" #define TAPE_COOKIE_TMPL "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x" -#define SCORE_COOKIE "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2" +#define SCORE_COOKIE_TMPL "ROCKSNDIAMONDS_SCORE_FILE_VERSION_x.x" // values for deciding when (not) to save configuration data #define SAVE_CONF_NEVER 0 @@ -257,6 +260,18 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = &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, -1, -1, @@ -307,6 +322,11 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = TYPE_BOOLEAN, CONF_VALUE_8_BIT(15), &li.lazy_relocation, FALSE }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), + &li.finish_dig_collect, TRUE + }, // (these values are different for each player) { @@ -768,9 +788,15 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = &li.android_clone_time, 10 }, { - EL_EMC_ANDROID, -1, + 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 }, @@ -809,7 +835,7 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = { EL_EMC_MAGIC_BALL, -1, TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), - &li.ball_state_initial, FALSE + &li.ball_active_initial, FALSE }, { EL_EMC_MAGIC_BALL, -1, @@ -1057,6 +1083,18 @@ static struct LevelFileConfigInfo chunk_config_CUSX_base[] = &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, @@ -2064,7 +2102,7 @@ static char *getPackedLevelBasename(int type) if ((dir = openDirectory(directory)) == NULL) { - Error(ERR_WARN, "cannot read current level directory '%s'", directory); + Warn("cannot read current level directory '%s'", directory); return basename; } @@ -2328,7 +2366,7 @@ static void copyLevelFileInfo(struct LevelFileInfo *lfi_from, // functions for loading R'n'D level // ---------------------------------------------------------------------------- -static int getMappedElement(int element) +int getMappedElement(int element) { // remap some (historic, now obsolete) elements @@ -2369,7 +2407,7 @@ static int getMappedElement(int element) default: if (element >= NUM_FILE_ELEMENTS) { - Error(ERR_WARN, "invalid level element %d", element); + Warn("invalid level element %d", element); element = EL_UNKNOWN; } @@ -2617,7 +2655,7 @@ static int LoadLevel_CNT2(File *file, int chunk_size, struct LevelInfo *level) } else { - Error(ERR_WARN, "cannot load content for element '%d'", element); + Warn("cannot load content for element '%d'", element); } return chunk_size; @@ -2677,7 +2715,7 @@ static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level) if (IS_CUSTOM_ELEMENT(element)) element_info[element].properties[EP_BITFIELD_BASE_NR] = properties; else - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); // older game versions that wrote level files with CUS1 chunks used // different default push delay values (not yet stored in level file) @@ -2710,7 +2748,7 @@ static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level) if (IS_CUSTOM_ELEMENT(element)) element_info[element].change->target_element = custom_target_element; else - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); } level->file_has_custom_elements = TRUE; @@ -2738,7 +2776,7 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) if (!IS_CUSTOM_ELEMENT(element)) { - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); element = EL_INTERNAL_DUMMY; } @@ -2824,9 +2862,10 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) if (!IS_CUSTOM_ELEMENT(element)) { - Error(ERR_WARN, "invalid custom element number %d", element); + Warn("invalid custom element number %d", element); ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size; } @@ -2972,9 +3011,10 @@ static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level) if (!IS_GROUP_ELEMENT(element)) { - Error(ERR_WARN, "invalid group element number %d", element); + Warn("invalid group element number %d", element); ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size; } @@ -3036,9 +3076,8 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, if (num_entities > max_num_entities) { - Error(ERR_WARN, - "truncating number of entities for element %d from %d to %d", - element, num_entities, max_num_entities); + Warn("truncating number of entities for element %d from %d to %d", + element, num_entities, max_num_entities); num_entities = max_num_entities; } @@ -3047,8 +3086,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, data_type == TYPE_CONTENT_LIST)) { // for element and content lists, zero entities are not allowed - Error(ERR_WARN, "found empty list of entities for element %d", - element); + Warn("found empty list of entities for element %d", element); // do not set "num_entities" here to prevent reading behind buffer @@ -3139,9 +3177,9 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN; int error_element = real_element; - Error(ERR_WARN, "cannot load micro chunk '%s(%d)' value for element %d ['%s']", - error_conf_chunk_bytes, error_conf_chunk_token, - error_element, EL_NAME(error_element)); + Warn("cannot load micro chunk '%s(%d)' value for element %d ['%s']", + error_conf_chunk_bytes, error_conf_chunk_token, + error_element, EL_NAME(error_element)); } return micro_chunk_size; @@ -3258,8 +3296,8 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) if (ei->num_change_pages == -1) { - Error(ERR_WARN, "LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", - EL_NAME(element)); + Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'", + EL_NAME(element)); ei->num_change_pages = 1; @@ -3346,7 +3384,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (level_info_only) return; - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); if (!setup.editor.use_template_for_new_levels) return; @@ -3373,7 +3411,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of level file '%s'", filename); + Warn("unknown format of level file '%s'", filename); closeFile(file); @@ -3392,7 +3430,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of level file '%s'", filename); + Warn("unknown format of level file '%s'", filename); closeFile(file); @@ -3403,7 +3441,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unsupported version of level file '%s'", filename); + Warn("unsupported version of level file '%s'", filename); closeFile(file); @@ -3464,15 +3502,17 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, if (chunk_info[i].name == NULL) { - Error(ERR_WARN, "unknown chunk '%s' in level file '%s'", - chunk_name, filename); + Warn("unknown chunk '%s' in level file '%s'", + chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); } else if (chunk_info[i].size != -1 && chunk_info[i].size != chunk_size) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); } else @@ -3486,8 +3526,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, // information, so check them here if (chunk_size_expected != chunk_size) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'", - chunk_size, chunk_name, filename); + Warn("wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); } } } @@ -3515,118 +3555,98 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) { 2, 2 }, }; struct LevelInfo_EM *level_em = level->native_em_level; - struct LEVEL *lev = level_em->lev; - struct PLAYER **ply = level_em->ply; + struct CAVE *cav = level_em->cav; int i, j, x, y; - lev->width = MIN(level->fieldx, EM_MAX_CAVE_WIDTH); - lev->height = MIN(level->fieldy, EM_MAX_CAVE_HEIGHT); + cav->width = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH); + cav->height = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT); - lev->time_seconds = level->time; - lev->required_initial = level->gems_needed; + cav->time_seconds = level->time; + cav->gems_needed = level->gems_needed; - lev->emerald_score = level->score[SC_EMERALD]; - lev->diamond_score = level->score[SC_DIAMOND]; - lev->alien_score = level->score[SC_ROBOT]; - lev->tank_score = level->score[SC_SPACESHIP]; - lev->bug_score = level->score[SC_BUG]; - lev->eater_score = level->score[SC_YAMYAM]; - lev->nut_score = level->score[SC_NUT]; - lev->dynamite_score = level->score[SC_DYNAMITE]; - lev->key_score = level->score[SC_KEY]; - lev->exit_score = level->score[SC_TIME_BONUS]; + cav->emerald_score = level->score[SC_EMERALD]; + cav->diamond_score = level->score[SC_DIAMOND]; + cav->alien_score = level->score[SC_ROBOT]; + cav->tank_score = level->score[SC_SPACESHIP]; + cav->bug_score = level->score[SC_BUG]; + cav->eater_score = level->score[SC_YAMYAM]; + cav->nut_score = level->score[SC_NUT]; + cav->dynamite_score = level->score[SC_DYNAMITE]; + cav->key_score = level->score[SC_KEY]; + cav->exit_score = level->score[SC_TIME_BONUS]; + + cav->num_eater_arrays = level->num_yamyam_contents; for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) - lev->eater_array[i][y * 3 + x] = - map_element_RND_to_EM(level->yamyam_content[i].e[x][y]); + cav->eater_array[i][y * 3 + x] = + map_element_RND_to_EM_cave(level->yamyam_content[i].e[x][y]); - lev->amoeba_time = level->amoeba_speed; - lev->wonderwall_time_initial = level->time_magic_wall; - lev->wheel_time = level->time_wheel; + cav->amoeba_time = level->amoeba_speed; + cav->wonderwall_time = level->time_magic_wall; + cav->wheel_time = level->time_wheel; - lev->android_move_time = level->android_move_time; - lev->android_clone_time = level->android_clone_time; - lev->ball_random = level->ball_random; - lev->ball_state_initial = level->ball_state_initial; - lev->ball_time = level->ball_time; - lev->num_ball_arrays = level->num_ball_contents; + cav->android_move_time = level->android_move_time; + cav->android_clone_time = level->android_clone_time; + cav->ball_random = level->ball_random; + cav->ball_active = level->ball_active_initial; + cav->ball_time = level->ball_time; + cav->num_ball_arrays = level->num_ball_contents; - lev->lenses_score = level->lenses_score; - lev->magnify_score = level->magnify_score; - lev->slurp_score = level->slurp_score; + cav->lenses_score = level->lenses_score; + cav->magnify_score = level->magnify_score; + cav->slurp_score = level->slurp_score; - lev->lenses_time = level->lenses_time; - lev->magnify_time = level->magnify_time; + cav->lenses_time = level->lenses_time; + cav->magnify_time = level->magnify_time; - lev->wind_direction_initial = + cav->wind_direction = map_direction_RND_to_EM(level->wind_direction_initial); - lev->wind_cnt_initial = (level->wind_direction_initial != MV_NONE ? - lev->wind_time : 0); for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (j = 0; j < 8; j++) - lev->ball_array[i][j] = - map_element_RND_to_EM(level-> - ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]]); + cav->ball_array[i][j] = + map_element_RND_to_EM_cave(level->ball_content[i]. + e[ball_xy[j][0]][ball_xy[j][1]]); map_android_clone_elements_RND_to_EM(level); - // first fill the complete playfield with the default border element + // first fill the complete playfield with the empty space element for (y = 0; y < EM_MAX_CAVE_HEIGHT; y++) for (x = 0; x < EM_MAX_CAVE_WIDTH; x++) - level_em->cave[x][y] = ZBORDER; - - if (BorderElement == EL_STEELWALL) - { - for (y = 0; y < lev->height + 2; y++) - for (x = 0; x < lev->width + 2; x++) - level_em->cave[x + 1][y + 1] = map_element_RND_to_EM(EL_STEELWALL); - } + cav->cave[x][y] = Cblank; // then copy the real level contents from level file into the playfield - for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++) + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { - int new_element = map_element_RND_to_EM(level->field[x][y]); - int offset = (BorderElement == EL_STEELWALL ? 1 : 0); - int xx = x + 1 + offset; - int yy = y + 1 + offset; + int new_element = map_element_RND_to_EM_cave(level->field[x][y]); if (level->field[x][y] == EL_AMOEBA_DEAD) - new_element = map_element_RND_to_EM(EL_AMOEBA_WET); + new_element = map_element_RND_to_EM_cave(EL_AMOEBA_WET); - level_em->cave[xx][yy] = new_element; + cav->cave[x][y] = new_element; } for (i = 0; i < MAX_PLAYERS; i++) { - ply[i]->x_initial = 0; - ply[i]->y_initial = 0; + cav->player_x[i] = -1; + cav->player_y[i] = -1; } // initialize player positions and delete players from the playfield - for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++) + for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { if (ELEM_IS_PLAYER(level->field[x][y])) { int player_nr = GET_PLAYER_NR(level->field[x][y]); - int offset = (BorderElement == EL_STEELWALL ? 1 : 0); - int xx = x + 1 + offset; - int yy = y + 1 + offset; - ply[player_nr]->x_initial = xx; - ply[player_nr]->y_initial = yy; + cav->player_x[player_nr] = x; + cav->player_y[player_nr] = y; - level_em->cave[xx][yy] = map_element_RND_to_EM(EL_EMPTY); + cav->cave[x][y] = map_element_RND_to_EM_cave(EL_EMPTY); } } - - if (BorderElement == EL_STEELWALL) - { - lev->width += 2; - lev->height += 2; - } } static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) @@ -3643,69 +3663,68 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) { 2, 2 }, }; struct LevelInfo_EM *level_em = level->native_em_level; - struct LEVEL *lev = level_em->lev; - struct PLAYER **ply = level_em->ply; + struct CAVE *cav = level_em->cav; int i, j, x, y; - level->fieldx = MIN(lev->width, MAX_LEV_FIELDX); - level->fieldy = MIN(lev->height, MAX_LEV_FIELDY); + level->fieldx = MIN(cav->width, MAX_LEV_FIELDX); + level->fieldy = MIN(cav->height, MAX_LEV_FIELDY); - level->time = lev->time_seconds; - level->gems_needed = lev->required_initial; + level->time = cav->time_seconds; + level->gems_needed = cav->gems_needed; sprintf(level->name, "Level %d", level->file_info.nr); - level->score[SC_EMERALD] = lev->emerald_score; - level->score[SC_DIAMOND] = lev->diamond_score; - level->score[SC_ROBOT] = lev->alien_score; - level->score[SC_SPACESHIP] = lev->tank_score; - level->score[SC_BUG] = lev->bug_score; - level->score[SC_YAMYAM] = lev->eater_score; - level->score[SC_NUT] = lev->nut_score; - level->score[SC_DYNAMITE] = lev->dynamite_score; - level->score[SC_KEY] = lev->key_score; - level->score[SC_TIME_BONUS] = lev->exit_score; + level->score[SC_EMERALD] = cav->emerald_score; + level->score[SC_DIAMOND] = cav->diamond_score; + level->score[SC_ROBOT] = cav->alien_score; + level->score[SC_SPACESHIP] = cav->tank_score; + level->score[SC_BUG] = cav->bug_score; + level->score[SC_YAMYAM] = cav->eater_score; + level->score[SC_NUT] = cav->nut_score; + level->score[SC_DYNAMITE] = cav->dynamite_score; + level->score[SC_KEY] = cav->key_score; + level->score[SC_TIME_BONUS] = cav->exit_score; - level->num_yamyam_contents = MAX_ELEMENT_CONTENTS; + level->num_yamyam_contents = cav->num_eater_arrays; - for (i = 0; i < level->num_yamyam_contents; i++) + for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) level->yamyam_content[i].e[x][y] = - map_element_EM_to_RND(lev->eater_array[i][y * 3 + x]); + map_element_EM_to_RND_cave(cav->eater_array[i][y * 3 + x]); - level->amoeba_speed = lev->amoeba_time; - level->time_magic_wall = lev->wonderwall_time_initial; - level->time_wheel = lev->wheel_time; + level->amoeba_speed = cav->amoeba_time; + level->time_magic_wall = cav->wonderwall_time; + level->time_wheel = cav->wheel_time; - level->android_move_time = lev->android_move_time; - level->android_clone_time = lev->android_clone_time; - level->ball_random = lev->ball_random; - level->ball_state_initial = lev->ball_state_initial; - level->ball_time = lev->ball_time; - level->num_ball_contents = lev->num_ball_arrays; + level->android_move_time = cav->android_move_time; + level->android_clone_time = cav->android_clone_time; + level->ball_random = cav->ball_random; + level->ball_active_initial = cav->ball_active; + level->ball_time = cav->ball_time; + level->num_ball_contents = cav->num_ball_arrays; - level->lenses_score = lev->lenses_score; - level->magnify_score = lev->magnify_score; - level->slurp_score = lev->slurp_score; + level->lenses_score = cav->lenses_score; + level->magnify_score = cav->magnify_score; + level->slurp_score = cav->slurp_score; - level->lenses_time = lev->lenses_time; - level->magnify_time = lev->magnify_time; + level->lenses_time = cav->lenses_time; + level->magnify_time = cav->magnify_time; level->wind_direction_initial = - map_direction_EM_to_RND(lev->wind_direction_initial); + map_direction_EM_to_RND(cav->wind_direction); for (i = 0; i < MAX_ELEMENT_CONTENTS; i++) for (j = 0; j < 8; j++) level->ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]] = - map_element_EM_to_RND(lev->ball_array[i][j]); + map_element_EM_to_RND_cave(cav->ball_array[i][j]); map_android_clone_elements_EM_to_RND(level); // convert the playfield (some elements need special treatment) for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++) { - int new_element = map_element_EM_to_RND(level_em->cave[x + 1][y + 1]); + int new_element = map_element_EM_to_RND_cave(cav->cave[x][y]); if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0) new_element = EL_AMOEBA_DEAD; @@ -3717,12 +3736,15 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) { // in case of all players set to the same field, use the first player int nr = MAX_PLAYERS - i - 1; - int jx = ply[nr]->x_initial - 1; - int jy = ply[nr]->y_initial - 1; + int jx = cav->player_x[nr]; + int jy = cav->player_y[nr]; if (jx != -1 && jy != -1) level->field[jx][jy] = EL_PLAYER_1 + nr; } + + // time score is counted for each 10 seconds left in Emerald Mine levels + level->time_score_base = 10; } @@ -3835,7 +3857,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) { num_invalid_elements++; - Error(ERR_DEBUG, "invalid element %d at position %d, %d", + Debug("level:native:SP", "invalid element %d at position %d, %d", element_old, x, y); } @@ -3844,8 +3866,8 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) } if (num_invalid_elements > 0) - Error(ERR_WARN, "found %d invalid elements%s", num_invalid_elements, - (!options.debug ? " (use '--debug' for more details)" : "")); + Warn("found %d invalid elements%s", num_invalid_elements, + (!options.debug ? " (use '--debug' for more details)" : "")); for (i = 0; i < MAX_PLAYERS; i++) level->initial_player_gravity[i] = @@ -3881,8 +3903,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) if (port_x < 0 || port_x >= level->fieldx || port_y < 0 || port_y >= level->fieldy) { - Error(ERR_WARN, "special port position (%d, %d) out of bounds", - port_x, port_y); + Warn("special port position (%d, %d) out of bounds", port_x, port_y); continue; } @@ -3892,7 +3913,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) if (port_element < EL_SP_GRAVITY_PORT_RIGHT || port_element > EL_SP_GRAVITY_PORT_UP) { - Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y); + Warn("no special port at position (%d, %d)", port_x, port_y); continue; } @@ -3917,12 +3938,11 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) level->time_wheel = 0; level->amoeba_content = EL_EMPTY; -#if 1 - // original Supaplex does not use score values -- use default values -#else + // original Supaplex does not use score values -- rate by playing time for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) level->score[i] = 0; -#endif + + level->rate_time_over_score = TRUE; // there are no yamyams in supaplex levels for (i = 0; i < level->num_yamyam_contents; i++) @@ -3958,8 +3978,8 @@ static void CopyNativeTape_RND_to_SP(struct LevelInfo *level) if (demo->length + demo_entries >= SP_MAX_TAPE_LEN) { - Error(ERR_WARN, "tape truncated: size exceeds maximum SP demo size %d", - SP_MAX_TAPE_LEN); + Warn("tape truncated: size exceeds maximum SP demo size %d", + SP_MAX_TAPE_LEN); break; } @@ -4003,7 +4023,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) int demo_repeat = (demo->data[i] & 0xf0) >> 4; int tape_action = map_key_SP_to_RND(demo_action); int tape_repeat = demo_repeat + 1; - byte action[MAX_PLAYERS] = { tape_action, 0, 0, 0 }; + byte action[MAX_TAPE_ACTIONS] = { tape_action }; boolean success = 0; int j; @@ -4012,8 +4032,8 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) if (!success) { - Error(ERR_WARN, "SP demo truncated: size exceeds maximum tape size %d", - MAX_TAPE_LEN); + Warn("SP demo truncated: size exceeds maximum tape size %d", + MAX_TAPE_LEN); break; } @@ -4288,7 +4308,7 @@ static int getMappedElement_DC(int element) break; case 0x13f5: - element = EL_YAMYAM; + element = EL_YAMYAM_UP; break; case 0x1425: @@ -5314,7 +5334,7 @@ static int getMappedElement_DC(int element) break; case 0x1682: // secret gate (red) - element = EL_GATE_1_GRAY; + element = EL_EM_GATE_1_GRAY; break; case 0x1683: // gate (yellow) @@ -5322,7 +5342,7 @@ static int getMappedElement_DC(int element) break; case 0x1684: // secret gate (yellow) - element = EL_GATE_2_GRAY; + element = EL_EM_GATE_2_GRAY; break; case 0x1685: // gate (blue) @@ -5330,7 +5350,7 @@ static int getMappedElement_DC(int element) break; case 0x1686: // secret gate (blue) - element = EL_GATE_4_GRAY; + element = EL_EM_GATE_4_GRAY; break; case 0x1687: // gate (green) @@ -5338,7 +5358,7 @@ static int getMappedElement_DC(int element) break; case 0x1688: // secret gate (green) - element = EL_GATE_3_GRAY; + element = EL_EM_GATE_3_GRAY; break; case 0x1689: // gate (white) @@ -5569,7 +5589,8 @@ static int getMappedElement_DC(int element) element = EL_INVISIBLE_SAND; else { - Error(ERR_WARN, "unknown Diamond Caves element 0x%04x", element); + Warn("unknown Diamond Caves element 0x%04x", element); + element = EL_UNKNOWN; } break; @@ -5617,7 +5638,7 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot decode level from stream -- using empty level"); + Warn("cannot decode level from stream -- using empty level"); return; } @@ -5730,9 +5751,19 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, level->extra_time = header[56] | (header[57] << 8); level->shield_normal_time = header[58] | (header[59] << 8); + // shield and extra time elements do not have a score + level->score[SC_SHIELD] = 0; + level->extra_time_score = 0; + + // set time for normal and deadly shields to the same value + level->shield_deadly_time = level->shield_normal_time; + // Diamond Caves has the same (strange) behaviour as Emerald Mine that gems // can slip down from flat walls, like normal walls and steel walls level->em_slippery_gems = TRUE; + + // time score is counted for each 10 seconds left in Diamond Caves levels + level->time_score_base = 10; } static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, @@ -5750,7 +5781,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, level->no_valid_file = TRUE; if (!level_info_only) - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -5768,8 +5799,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown DC level file '%s' -- using empty level", - filename); + Warn("unknown DC level file '%s' -- using empty level", filename); return; } @@ -5795,8 +5825,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", - filename); + Warn("cannot fseek in file '%s' -- using empty level", filename); return; } @@ -5814,8 +5843,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "unknown DC2 level file '%s' -- using empty level", - filename); + Warn("unknown DC2 level file '%s' -- using empty level", filename); return; } @@ -5890,7 +5918,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, level->no_valid_file = TRUE; if (!level_info_only) - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -6064,7 +6092,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, { level->no_valid_file = TRUE; - Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename); + Warn("cannot read level '%s' -- using empty level", filename); return; } @@ -6259,7 +6287,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) if (level->game_version < VERSION_IDENT(3,2,0,5)) { // time bonus score was given for 10 s instead of 1 s before 3.2.0-5 - level->score[SC_TIME_BONUS] /= 10; + level->time_score_base = 10; } if (leveldir_current->latest_engine) @@ -6431,6 +6459,10 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) // only Sokoban fields (but not objects) had to be solved before 4.1.1.1 if (level->game_version < VERSION_IDENT(4,1,1,1)) level->sb_objects_needed = FALSE; + + // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0 + if (level->game_version <= VERSION_IDENT(4,2,2,0)) + level->finish_dig_collect = FALSE; } static void LoadLevel_InitStandardElements(struct LevelInfo *level) @@ -6578,6 +6610,27 @@ static void LoadLevel_InitCustomElements(struct LevelInfo *level) element_info[element].ignition_delay = 8; } } + + // set mouse click change events to work for left/middle/right mouse button + if (level->game_version < VERSION_IDENT(4,2,3,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; + + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (change->has_event[CE_CLICKED_BY_MOUSE] || + change->has_event[CE_PRESSED_BY_MOUSE] || + change->has_event[CE_MOUSE_CLICKED_ON_X] || + change->has_event[CE_MOUSE_PRESSED_ON_X]) + change->trigger_side = CH_SIDE_ANY; + } + } + } } static void LoadLevel_InitElements(struct LevelInfo *level) @@ -6611,7 +6664,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level) // copy elements to runtime playfield array for (x = 0; x < MAX_LEV_FIELDX; x++) for (y = 0; y < MAX_LEV_FIELDY; y++) - Feld[x][y] = level->field[x][y]; + Tile[x][y] = level->field[x][y]; // initialize level size variables for faster access lev_fieldx = level->fieldx; @@ -6646,7 +6699,7 @@ void LoadLevelTemplate(int nr) { if (!fileExists(getGlobalLevelTemplateFilename())) { - Error(ERR_WARN, "no level template found for this level"); + Warn("no level template found for this level"); return; } @@ -6809,8 +6862,8 @@ static int SaveLevel_BODY(FILE *file, struct LevelInfo *level) int chunk_size = 0; int x, y; - for (y = 0; y < level->fieldy; y++) - for (x = 0; x < level->fieldx; x++) + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) if (level->encoding_16bit_field) chunk_size += putFile16BitBE(file, level->field[x][y]); else @@ -6887,7 +6940,8 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) // chunk header already written -- write empty chunk data WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE); - Error(ERR_WARN, "cannot save content for element '%d'", element); + Warn("cannot save content for element '%d'", element); + return; } @@ -6955,7 +7009,7 @@ static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom element properties"); + Warn("inconsistent number of custom element properties"); } #endif @@ -6984,7 +7038,7 @@ static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom target elements"); + Warn("inconsistent number of custom target elements"); } #endif @@ -7067,7 +7121,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, } if (check != num_changed_custom_elements) // should not happen - Error(ERR_WARN, "inconsistent number of custom element properties"); + Warn("inconsistent number of custom element properties"); } #endif @@ -7445,7 +7499,8 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot save level file '%s'", filename); + Warn("cannot save level file '%s'", filename); + return; } @@ -7570,7 +7625,7 @@ void DumpLevel(struct LevelInfo *level) { if (level->no_level_file || level->no_valid_file) { - Error(ERR_WARN, "cannot dump -- no valid level file found"); + Warn("cannot dump -- no valid level file found"); return; } @@ -7625,6 +7680,8 @@ static void setTapeInfoToDefaults(void) // at least one (default: the first) player participates in every tape tape.num_participating_players = 1; + tape.property_bits = TAPE_PROPERTY_NONE; + tape.level_nr = level_nr; tape.counter = 0; tape.changed = FALSE; @@ -7633,9 +7690,48 @@ static void setTapeInfoToDefaults(void) tape.playing = FALSE; tape.pausing = FALSE; + tape.scr_fieldx = SCR_FIELDX_DEFAULT; + tape.scr_fieldy = SCR_FIELDY_DEFAULT; + tape.no_valid_file = FALSE; } +static int getTapePosSize(struct TapeInfo *tape) +{ + int tape_pos_size = 0; + + if (tape->use_key_actions) + tape_pos_size += tape->num_participating_players; + + if (tape->use_mouse_actions) + tape_pos_size += 3; // x and y position and mouse button mask + + tape_pos_size += 1; // tape action delay value + + return tape_pos_size; +} + +static void setTapeActionFlags(struct TapeInfo *tape, int value) +{ + tape->use_key_actions = FALSE; + tape->use_mouse_actions = FALSE; + + if (value != TAPE_USE_MOUSE_ACTIONS_ONLY) + tape->use_key_actions = TRUE; + + if (value != TAPE_USE_KEY_ACTIONS_ONLY) + tape->use_mouse_actions = TRUE; +} + +static int getTapeActionValue(struct TapeInfo *tape) +{ + return (tape->use_key_actions && + tape->use_mouse_actions ? TAPE_USE_KEY_AND_MOUSE_ACTIONS : + tape->use_key_actions ? TAPE_USE_KEY_ACTIONS_ONLY : + tape->use_mouse_actions ? TAPE_USE_MOUSE_ACTIONS_ONLY : + TAPE_ACTIONS_DEFAULT); +} + static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape) { tape->file_version = getFileVersion(file); @@ -7671,7 +7767,9 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) } } - tape->use_mouse = (getFile8Bit(file) == 1 ? TRUE : FALSE); + setTapeActionFlags(tape, getFile8Bit(file)); + + tape->property_bits = getFile8Bit(file); ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED); @@ -7685,18 +7783,31 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) return chunk_size; } +static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape) +{ + tape->scr_fieldx = getFile8Bit(file); + tape->scr_fieldy = getFile8Bit(file); + + return chunk_size; +} + static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) { + char *level_identifier = NULL; int level_identifier_size; int i; level_identifier_size = getFile16BitBE(file); - tape->level_identifier = - checked_realloc(tape->level_identifier, level_identifier_size); + level_identifier = checked_malloc(level_identifier_size); for (i = 0; i < level_identifier_size; i++) - tape->level_identifier[i] = getFile8Bit(file); + level_identifier[i] = getFile8Bit(file); + + strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN); + tape->level_identifier[MAX_FILENAME_LEN] = '\0'; + + checked_free(level_identifier); tape->level_nr = getFile16BitBE(file); @@ -7708,8 +7819,7 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) { int i, j; - int tape_pos_size = - (tape->use_mouse ? 3 : tape->num_participating_players) + 1; + int tape_pos_size = getTapePosSize(tape); int chunk_size_expected = tape_pos_size * tape->length; if (chunk_size_expected != chunk_size) @@ -7722,25 +7832,17 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) { if (i >= MAX_TAPE_LEN) { - Error(ERR_WARN, "tape truncated -- size exceeds maximum tape size %d", + Warn("tape truncated -- size exceeds maximum tape size %d", MAX_TAPE_LEN); // tape too large; read and ignore remaining tape data from this chunk for (;i < tape->length; i++) - ReadUnusedBytesFromFile(file, tape->num_participating_players + 1); + ReadUnusedBytesFromFile(file, tape_pos_size); break; } - if (tape->use_mouse) - { - tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file); - tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file); - tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file); - - tape->pos[i].action[TAPE_ACTION_UNUSED] = 0; - } - else + if (tape->use_key_actions) { for (j = 0; j < MAX_PLAYERS; j++) { @@ -7751,6 +7853,13 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape) } } + if (tape->use_mouse_actions) + { + tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file); + tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file); + tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file); + } + tape->pos[i].delay = getFile8Bit(file); if (tape->file_version == FILE_VERSION_1_0) @@ -7869,7 +7978,7 @@ static void LoadTape_SokobanSolution(char *filename) default: tape.no_valid_file = TRUE; - Error(ERR_WARN, "unsupported Sokoban solution file '%s' ['%d']", filename, c); + Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c); break; } @@ -7918,7 +8027,7 @@ void LoadTapeFromFilename(char *filename) { tape.no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of tape file '%s'", filename); + Warn("unknown format of tape file '%s'", filename); closeFile(file); @@ -7937,7 +8046,7 @@ void LoadTapeFromFilename(char *filename) { tape.no_valid_file = TRUE; - Error(ERR_WARN, "unknown format of tape file '%s'", filename); + Warn("unknown format of tape file '%s'", filename); closeFile(file); @@ -7948,7 +8057,7 @@ void LoadTapeFromFilename(char *filename) { tape.no_valid_file = TRUE; - Error(ERR_WARN, "unsupported version of tape file '%s'", filename); + Warn("unsupported version of tape file '%s'", filename); closeFile(file); @@ -7977,6 +8086,7 @@ void LoadTapeFromFilename(char *filename) { { "VERS", TAPE_CHUNK_VERS_SIZE, LoadTape_VERS }, { "HEAD", TAPE_CHUNK_HEAD_SIZE, LoadTape_HEAD }, + { "SCRN", TAPE_CHUNK_SCRN_SIZE, LoadTape_SCRN }, { "INFO", -1, LoadTape_INFO }, { "BODY", -1, LoadTape_BODY }, { NULL, 0, NULL } @@ -7992,15 +8102,17 @@ void LoadTapeFromFilename(char *filename) if (chunk_info[i].name == NULL) { - Error(ERR_WARN, "unknown chunk '%s' in tape file '%s'", + Warn("unknown chunk '%s' in tape file '%s'", chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); } else if (chunk_info[i].size != -1 && chunk_info[i].size != chunk_size) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'", + Warn("wrong size (%d) of chunk '%s' in tape file '%s'", chunk_size, chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); } else @@ -8014,7 +8126,7 @@ void LoadTapeFromFilename(char *filename) // information, so check them here if (chunk_size_expected != chunk_size) { - Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'", + Warn("wrong size (%d) of chunk '%s' in tape file '%s'", chunk_size, chunk_name, filename); } } @@ -8027,9 +8139,12 @@ void LoadTapeFromFilename(char *filename) tape.length_seconds = GetTapeLengthSeconds(); #if 0 - printf("::: tape file version: %d\n", tape.file_version); - printf("::: tape game version: %d\n", tape.game_version); - printf("::: tape engine version: %d\n", tape.engine_version); + Debug("files:LoadTapeFromFilename", "tape file version: %d", + tape.file_version); + Debug("files:LoadTapeFromFilename", "tape game version: %d", + tape.game_version); + Debug("files:LoadTapeFromFilename", "tape engine version: %d", + tape.engine_version); #endif } @@ -8052,6 +8167,14 @@ void LoadSolutionTape(int nr) CopyNativeTape_SP_to_RND(&level); } +static boolean checkSaveTape_SCRN(struct TapeInfo *tape) +{ + // chunk required for team mode tapes with non-default screen size + return (tape->num_participating_players > 1 && + (tape->scr_fieldx != SCR_FIELDX_DEFAULT || + tape->scr_fieldy != SCR_FIELDY_DEFAULT)); +} + static void SaveTape_VERS(FILE *file, struct TapeInfo *tape) { putFileVersion(file, tape->file_version); @@ -8074,7 +8197,9 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) putFile8Bit(file, store_participating_players); - putFile8Bit(file, (tape->use_mouse ? 1 : 0)); + 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); @@ -8082,6 +8207,12 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) putFileVersion(file, tape->engine_version); } +static void SaveTape_SCRN(FILE *file, struct TapeInfo *tape) +{ + putFile8Bit(file, tape->scr_fieldx); + putFile8Bit(file, tape->scr_fieldy); +} + static void SaveTape_INFO(FILE *file, struct TapeInfo *tape) { int level_identifier_size = strlen(tape->level_identifier) + 1; @@ -8101,50 +8232,39 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) for (i = 0; i < tape->length; i++) { - if (tape->use_mouse) - { - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]); - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]); - putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]); - } - else + if (tape->use_key_actions) { for (j = 0; j < MAX_PLAYERS; j++) if (tape->player_participates[j]) putFile8Bit(file, tape->pos[i].action[j]); } + if (tape->use_mouse_actions) + { + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]); + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]); + putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]); + } + putFile8Bit(file, tape->pos[i].delay); } } -void SaveTape(int nr) +void SaveTapeToFilename(char *filename) { - char *filename = getTapeFilename(nr); FILE *file; - int num_participating_players = 0; int tape_pos_size; int info_chunk_size; int body_chunk_size; - int i; - - InitTapeDirectory(leveldir_current->subdir); if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot save level recording file '%s'", filename); + Warn("cannot save level recording file '%s'", filename); + return; } - tape.file_version = FILE_VERSION_ACTUAL; - tape.game_version = GAME_VERSION_ACTUAL; - - // count number of participating players - for (i = 0; i < MAX_PLAYERS; i++) - if (tape.player_participates[i]) - num_participating_players++; - - tape_pos_size = (tape.use_mouse ? 3 : num_participating_players) + 1; + tape_pos_size = getTapePosSize(&tape); info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2; body_chunk_size = tape_pos_size * tape.length; @@ -8158,6 +8278,12 @@ void SaveTape(int nr) putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE); SaveTape_HEAD(file, &tape); + if (checkSaveTape_SCRN(&tape)) + { + putFileChunkBE(file, "SCRN", TAPE_CHUNK_SCRN_SIZE); + SaveTape_SCRN(file, &tape); + } + putFileChunkBE(file, "INFO", info_chunk_size); SaveTape_INFO(file, &tape); @@ -8167,10 +8293,46 @@ void SaveTape(int nr) fclose(file); SetFilePermissions(filename, PERMS_PRIVATE); +} + +static void SaveTapeExt(char *filename) +{ + int i; + + tape.file_version = FILE_VERSION_ACTUAL; + tape.game_version = GAME_VERSION_ACTUAL; + + tape.num_participating_players = 0; + + // count number of participating players + for (i = 0; i < MAX_PLAYERS; i++) + if (tape.player_participates[i]) + tape.num_participating_players++; + + SaveTapeToFilename(filename); tape.changed = FALSE; } +void SaveTape(int nr) +{ + char *filename = getTapeFilename(nr); + + InitTapeDirectory(leveldir_current->subdir); + + SaveTapeExt(filename); +} + +void SaveScoreTape(int nr) +{ + char *filename = getScoreTapeFilename(tape.score_tape_basename, nr); + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreTapeDirectory(levelset.identifier, nr); + + SaveTapeExt(filename); +} + static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved, unsigned int req_state_added) { @@ -8209,7 +8371,7 @@ void DumpTape(struct TapeInfo *tape) if (tape->no_valid_file) { - Error(ERR_WARN, "cannot dump -- no valid tape file found"); + Warn("cannot dump -- no valid tape file found"); return; } @@ -8262,7 +8424,20 @@ void DumpTape(struct TapeInfo *tape) // score file functions // ============================================================================ -void LoadScore(int nr) +static void setScoreInfoToDefaults(void) +{ + int i; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + strcpy(scores.entry[i].tape_basename, UNDEFINED_FILENAME); + strcpy(scores.entry[i].name, EMPTY_PLAYER_NAME); + scores.entry[i].score = 0; + scores.entry[i].time = 0; + } +} + +static void LoadScore_OLD(int nr) { int i; char *filename = getScoreFilename(nr); @@ -8271,13 +8446,6 @@ void LoadScore(int nr) char *line_ptr; FILE *file; - // always start with reliable default values - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - { - strcpy(highscore[i].Name, EMPTY_PLAYER_NAME); - highscore[i].Score = 0; - } - if (!(file = fopen(filename, MODE_READ))) return; @@ -8287,17 +8455,20 @@ void LoadScore(int nr) if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') cookie[strlen(cookie) - 1] = '\0'; - if (!checkCookieString(cookie, SCORE_COOKIE)) + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) { - Error(ERR_WARN, "unknown format of score file '%s'", filename); + Warn("unknown format of score file '%s'", filename); + fclose(file); + return; } for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (fscanf(file, "%d", &highscore[i].Score) == EOF) - Error(ERR_WARN, "fscanf() failed; %s", strerror(errno)); + if (fscanf(file, "%d", &scores.entry[i].score) == EOF) + Warn("fscanf() failed; %s", strerror(errno)); + if (fgets(line, MAX_LINE_LEN, file) == NULL) line[0] = '\0'; @@ -8308,8 +8479,8 @@ void LoadScore(int nr) { if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') { - strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN); - highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0'; + strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN); + scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; break; } } @@ -8318,47 +8489,427 @@ void LoadScore(int nr) fclose(file); } -void SaveScore(int nr) +static void ConvertScore_OLD(void) +{ + // only convert score to time for levels that rate playing time over score + if (!level.rate_time_over_score) + return; + + // convert old score to playing time for score-less levels (like Supaplex) + int time_final_max = 999; + int i; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + int score = scores.entry[i].score; + + if (score > 0 && score < time_final_max) + scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND; + } +} + +static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores) +{ + scores->file_version = getFileVersion(file); + scores->game_version = getFileVersion(file); + + return chunk_size; +} + +static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) { + char *level_identifier = NULL; + int level_identifier_size; int i; - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); - char *filename = getScoreFilename(nr); - FILE *file; - // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + level_identifier_size = getFile16BitBE(file); - if (!(file = fopen(filename, MODE_WRITE))) + level_identifier = checked_malloc(level_identifier_size); + + for (i = 0; i < level_identifier_size; i++) + level_identifier[i] = getFile8Bit(file); + + strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN); + scores->level_identifier[MAX_FILENAME_LEN] = '\0'; + + checked_free(level_identifier); + + scores->level_nr = getFile16BitBE(file); + scores->num_entries = getFile16BitBE(file); + + chunk_size = 2 + level_identifier_size + 2 + 2; + + return chunk_size; +} + +static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) { - Error(ERR_WARN, "cannot save score for level %d", nr); - return; + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + scores->entry[i].name[j] = getFile8Bit(file); + + scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; } - fprintf(file, "%s\n\n", SCORE_COOKIE); + chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN; - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name); + return chunk_size; +} - fclose(file); +static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; - SetFilePermissions(filename, permissions); + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile16BitBE(file); + + chunk_size = scores->num_entries * 2; + + return chunk_size; } +static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; -// ============================================================================ -// setup file functions -// ============================================================================ + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].time = getFile32BitBE(file); -#define TOKEN_STR_PLAYER_PREFIX "player_" + chunk_size = scores->num_entries * 4; + return chunk_size; +} -static struct TokenInfo global_setup_tokens[] = +static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) { + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + scores->entry[i].tape_basename[j] = getFile8Bit(file); + + scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + return chunk_size; +} + +void LoadScore(int nr) +{ + char *filename = getScoreFilename(nr); + char cookie[MAX_LINE_LEN]; + char chunk_name[CHUNK_ID_LEN + 1]; + int chunk_size; + boolean old_score_file_format = FALSE; + File *file; + + // always start with reliable default values + setScoreInfoToDefaults(); + + if (!(file = openFile(filename, MODE_READ))) + return; + + getFileChunkBE(file, chunk_name, NULL); + if (strEqual(chunk_name, "RND1")) + { + getFile32BitBE(file); // not used + + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "SCOR")) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + } + else // check for old file format with cookie string + { + strcpy(cookie, chunk_name); + if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) + cookie[4] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + + old_score_file_format = TRUE; + } + + if (old_score_file_format) + { + // score files from versions before 4.2.4.0 without chunk structure + LoadScore_OLD(nr); + + // convert score to time, if possible (mainly for Supaplex levels) + ConvertScore_OLD(); + } + else + { + static struct + { + char *name; + int size; + int (*loader)(File *, int, struct ScoreInfo *); + } + chunk_info[] = + { + { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS }, + { "INFO", -1, LoadScore_INFO }, + { "NAME", -1, LoadScore_NAME }, + { "SCOR", -1, LoadScore_SCOR }, + { "TIME", -1, LoadScore_TIME }, + { "TAPE", -1, LoadScore_TAPE }, + + { NULL, 0, NULL } + }; + + while (getFileChunkBE(file, chunk_name, &chunk_size)) + { + int i = 0; + + while (chunk_info[i].name != NULL && + !strEqual(chunk_name, chunk_info[i].name)) + i++; + + if (chunk_info[i].name == NULL) + { + Warn("unknown chunk '%s' in score file '%s'", + chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this score chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &scores); + + // the size of some chunks cannot be checked before reading other + // chunks first (like "HEAD" and "BODY") that contain some header + // information, so check them here + if (chunk_size_expected != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); + } + } + } + } + + closeFile(file); +} + +#if ENABLE_HISTORIC_CHUNKS +void SaveScore_OLD(int nr) +{ + int i; + int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); + char *filename = getScoreFilename(nr); + FILE *file; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + 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, permissions); +} +#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 permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); + 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, permissions); +} + +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); +} + + +// ============================================================================ +// setup file functions +// ============================================================================ + +#define TOKEN_STR_PLAYER_PREFIX "player_" + + +static struct TokenInfo global_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_name, "player_name" + }, + { + TYPE_SWITCH, + &setup.multiple_users, "multiple_users" + }, { - TYPE_STRING, - &setup.player_name, "player_name" - }, - { TYPE_SWITCH, &setup.sound, "sound" }, @@ -8382,6 +8933,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.scroll_delay, "scroll_delay" }, + { + TYPE_SWITCH, + &setup.forced_scroll_delay, "forced_scroll_delay" + }, { TYPE_INTEGER, &setup.scroll_delay_value, "scroll_delay_value" @@ -8432,7 +8987,11 @@ static struct TokenInfo global_setup_tokens[] = }, { TYPE_SWITCH, - &setup.skip_scores_after_game, "skip_scores_after_game" + &setup.count_score_after_game, "count_score_after_game" + }, + { + TYPE_SWITCH, + &setup.show_scores_after_game, "show_scores_after_game" }, { TYPE_SWITCH, @@ -8470,6 +9029,14 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.ask_on_game_over, "ask_on_game_over" }, + { + TYPE_SWITCH, + &setup.ask_on_quit_game, "ask_on_quit_game" + }, + { + TYPE_SWITCH, + &setup.ask_on_quit_program, "ask_on_quit_program" + }, { TYPE_SWITCH, &setup.quick_switch, "quick_player_switch" @@ -8482,6 +9049,14 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.prefer_aga_graphics, "prefer_aga_graphics" }, + { + TYPE_SWITCH, + &setup.prefer_lowpass_sounds, "prefer_lowpass_sounds" + }, + { + TYPE_SWITCH, + &setup.prefer_extra_panel_items, "prefer_extra_panel_items" + }, { TYPE_SWITCH, &setup.game_speed_extended, "game_speed_extended" @@ -8626,6 +9201,10 @@ static struct TokenInfo editor_setup_tokens[] = TYPE_SWITCH, &setup.editor.show_element_token, "editor.show_element_token" }, + { + TYPE_SWITCH, + &setup.editor.show_read_only_warning, "editor.show_read_only_warning" + }, }; static struct TokenInfo editor_cascade_setup_tokens[] = @@ -8859,6 +9438,10 @@ static struct TokenInfo player_setup_tokens[] = static struct TokenInfo system_setup_tokens[] = { + { + TYPE_STRING, + &setup.system.sdl_renderdriver, "system.sdl_renderdriver" + }, { TYPE_STRING, &setup.system.sdl_videodriver, "system.sdl_videodriver" @@ -8951,6 +9534,10 @@ static struct TokenInfo internal_setup_tokens[] = 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.menu_game, "menu_game" @@ -9086,6 +9673,14 @@ static struct TokenInfo debug_setup_tokens[] = TYPE_BOOLEAN, &setup.debug.show_frames_per_second, "debug.show_frames_per_second" }, + { + TYPE_SWITCH3, + &setup.debug.xsn_mode, "debug.xsn_mode" + }, + { + TYPE_INTEGER, + &setup.debug.xsn_percent, "debug.xsn_percent" + }, }; static struct TokenInfo options_setup_tokens[] = @@ -9096,26 +9691,13 @@ static struct TokenInfo options_setup_tokens[] = }, }; -static char *get_corrected_login_name(char *login_name) -{ - // needed because player name must be a fixed length string - char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1); - - strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN); - login_name_new[MAX_PLAYER_NAME_LEN] = '\0'; - - if (strlen(login_name) > MAX_PLAYER_NAME_LEN) // name has been cut - if (strchr(login_name_new, ' ')) - *strchr(login_name_new, ' ') = '\0'; - - return login_name_new; -} - static void setSetupInfoToDefaults(struct SetupInfo *si) { int i; - si->player_name = get_corrected_login_name(getLoginName()); + si->player_name = getStringCopy(getDefaultUserName(user.nr)); + + si->multiple_users = TRUE; si->sound = TRUE; si->sound_loops = TRUE; @@ -9123,6 +9705,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sound_simple = TRUE; si->toons = TRUE; si->scroll_delay = TRUE; + si->forced_scroll_delay = FALSE; si->scroll_delay_value = STD_SCROLL_DELAY; si->engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_DEFAULT); si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; @@ -9135,7 +9718,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->skip_levels = TRUE; si->increment_levels = TRUE; si->auto_play_next_level = TRUE; - si->skip_scores_after_game = FALSE; + si->count_score_after_game = TRUE; + si->show_scores_after_game = TRUE; si->time_limit = TRUE; si->fullscreen = FALSE; si->window_scaling_percent = STD_WINDOW_SCALING_PERCENT; @@ -9145,9 +9729,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->ask_on_escape = TRUE; si->ask_on_escape_editor = TRUE; si->ask_on_game_over = TRUE; + si->ask_on_quit_game = TRUE; + si->ask_on_quit_program = TRUE; si->quick_switch = FALSE; si->input_on_focus = FALSE; si->prefer_aga_graphics = TRUE; + si->prefer_lowpass_sounds = FALSE; + si->prefer_extra_panel_items = TRUE; si->game_speed_extended = FALSE; si->game_frame_delay = GAME_FRAME_DELAY; si->sp_show_border_elements = FALSE; @@ -9251,6 +9839,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->editor.show_element_token = FALSE; + si->editor.show_read_only_warning = TRUE; + si->editor.use_template_for_new_levels = TRUE; si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; @@ -9299,6 +9889,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->input[i].key.drop = (i == 0 ? DEFAULT_KEY_DROP : KSYM_UNDEFINED); } + si->system.sdl_renderdriver = getStringCopy(ARG_DEFAULT); si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT); si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT); si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE; @@ -9324,6 +9915,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET); si->internal.choose_from_top_leveldir = FALSE; si->internal.show_scaling_in_title = TRUE; + si->internal.create_user_levelset = TRUE; si->internal.default_window_width = WIN_XSIZE_DEFAULT; si->internal.default_window_height = WIN_YSIZE_DEFAULT; @@ -9355,11 +9947,16 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->debug.show_frames_per_second = FALSE; + si->debug.xsn_mode = AUTO; + si->debug.xsn_percent = 0; + si->options.verbose = FALSE; #if defined(PLATFORM_ANDROID) si->fullscreen = TRUE; #endif + + setHideSetupEntry(&setup.debug.xsn_mode); } static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) @@ -9406,10 +10003,21 @@ void setHideSetupEntry(void *setup_value) { char *hide_setup_token = getHideSetupToken(setup_value); + if (hide_setup_hash == NULL) + hide_setup_hash = newSetupFileHash(); + if (setup_value != NULL) setHashEntry(hide_setup_hash, hide_setup_token, ""); } +void removeHideSetupEntry(void *setup_value) +{ + char *hide_setup_token = getHideSetupToken(setup_value); + + if (setup_value != NULL) + removeHashEntry(hide_setup_hash, hide_setup_token); +} + boolean hideSetupEntry(void *setup_value) { char *hide_setup_token = getHideSetupToken(setup_value); @@ -9431,6 +10039,8 @@ static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash, // check if this setup option should be hidden in the setup menu if (token_hide_value != NULL && get_boolean_from_string(token_hide_value)) setHideSetupEntry(token_info[token_nr].value); + + free(token_hide_text); } static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, @@ -9448,9 +10058,6 @@ static void decodeSetupFileHash(SetupFileHash *setup_file_hash) if (!setup_file_hash) return; - if (hide_setup_hash == NULL) - hide_setup_hash = newSetupFileHash(); - for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++) setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i); @@ -9557,6 +10164,43 @@ static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) editor_cascade_setup_tokens[i].text)); } +void LoadUserNames(void) +{ + int last_user_nr = user.nr; + int i; + + if (global.user_names != NULL) + { + for (i = 0; i < MAX_PLAYER_NAMES; i++) + checked_free(global.user_names[i]); + + checked_free(global.user_names); + } + + global.user_names = checked_calloc(MAX_PLAYER_NAMES * sizeof(char *)); + + for (i = 0; i < MAX_PLAYER_NAMES; i++) + { + user.nr = i; + + SetupFileHash *setup_file_hash = loadSetupFileHash(getSetupFilename()); + + if (setup_file_hash) + { + char *player_name = getHashEntry(setup_file_hash, "player_name"); + + global.user_names[i] = getFixedUserName(player_name); + + freeSetupFileHash(setup_file_hash); + } + + if (global.user_names[i] == NULL) + global.user_names[i] = getStringCopy(getDefaultUserName(i)); + } + + user.nr = last_user_nr; +} + void LoadSetupFromFilename(char *filename) { SetupFileHash *setup_file_hash = loadSetupFileHash(filename); @@ -9569,7 +10213,7 @@ void LoadSetupFromFilename(char *filename) } else { - Error(ERR_DEBUG, "using default setup values"); + Debug("setup", "using default setup values"); } } @@ -9578,7 +10222,7 @@ static void LoadSetup_SpecialPostProcessing(void) char *player_name_new; // needed to work around problems with fixed length strings - player_name_new = get_corrected_login_name(setup.player_name); + player_name_new = getFixedUserName(setup.player_name); free(setup.player_name); setup.player_name = player_name_new; @@ -9686,7 +10330,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, if (!(file = fopen(filename, MODE_READ))) { - Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename); + Warn("cannot read game controller mappings file '%s'", filename); return; } @@ -9714,7 +10358,8 @@ void SaveSetup(void) if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot write setup file '%s'", filename); + Warn("cannot write setup file '%s'", filename); + return; } @@ -9723,7 +10368,8 @@ void SaveSetup(void) for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++) { // just to make things nicer :) - if (global_setup_tokens[i].value == &setup.sound || + if (global_setup_tokens[i].value == &setup.multiple_users || + global_setup_tokens[i].value == &setup.sound || global_setup_tokens[i].value == &setup.graphics_set || global_setup_tokens[i].value == &setup.volume_simple || global_setup_tokens[i].value == &setup.network_mode || @@ -9791,7 +10437,9 @@ void SaveSetup(void) fprintf(file, "\n"); for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++) - fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); + if (!strPrefix(debug_setup_tokens[i].text, "debug.xsn_") || + setup.debug.xsn_mode != AUTO) + fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); fprintf(file, "\n"); for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) @@ -9812,8 +10460,10 @@ void SaveSetup_AutoSetup(void) if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot write auto setup file '%s'", filename); + Warn("cannot write auto setup file '%s'", filename); + free(filename); + return; } @@ -9839,8 +10489,10 @@ void SaveSetup_EditorCascade(void) if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot write editor cascade state file '%s'", filename); + Warn("cannot write editor cascade state file '%s'", filename); + free(filename); + return; } @@ -9863,7 +10515,7 @@ static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, if (!(file = fopen(filename, MODE_WRITE))) { - Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename); + Warn("cannot write game controller mappings file '%s'", filename); return; } @@ -9936,7 +10588,7 @@ static int getElementFromToken(char *token) if (value != NULL) return atoi(value); - Error(ERR_WARN, "unknown element token '%s'", token); + Warn("unknown element token '%s'", token); return EL_UNDEFINED; } @@ -10193,6 +10845,10 @@ static int get_anim_parameter_values(char *s) static int get_anim_action_parameter_value(char *token) { + // check most common default case first to massively speed things up + if (strEqual(token, ARG_UNDEFINED)) + return ANIM_EVENT_ACTION_NONE; + int result = getImageIDFromToken(token); if (result == -1) @@ -10290,7 +10946,10 @@ int get_parameter_value(char *value_raw, char *suffix, int type) { result = get_anim_parameter_values(value); } - else if (strEqual(suffix, ".init_event_action") || + else if (strEqual(suffix, ".init_delay_action") || + strEqual(suffix, ".anim_delay_action") || + strEqual(suffix, ".post_delay_action") || + strEqual(suffix, ".init_event_action") || strEqual(suffix, ".anim_event_action")) { result = get_anim_action_parameter_value(value_raw); @@ -10313,6 +10972,12 @@ int get_parameter_value(char *value_raw, char *suffix, int type) if (string_has_parameter(value, "reverse")) result |= STYLE_REVERSE; + if (string_has_parameter(value, "leftmost_position")) + result |= STYLE_LEFTMOST_POSITION; + + if (string_has_parameter(value, "block_clicks")) + result |= STYLE_BLOCK; + if (string_has_parameter(value, "passthrough_clicks")) result |= STYLE_PASSTHROUGH; @@ -10328,6 +10993,12 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "curtain") ? FADE_MODE_CURTAIN : FADE_MODE_DEFAULT); } + else if (strEqual(suffix, ".auto_delay_unit")) + { + result = (string_has_parameter(value, "ms") ? AUTO_DELAY_UNIT_MS : + string_has_parameter(value, "frames") ? AUTO_DELAY_UNIT_FRAMES : + AUTO_DELAY_UNIT_DEFAULT); + } else if (strPrefix(suffix, ".font")) // (may also be ".font_xyz") { result = gfx.get_font_from_token_function(value); @@ -10394,10 +11065,14 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void) title_initial_first_default.post_delay; titlescreen_initial_first_default.auto_delay = title_initial_first_default.auto_delay; + titlescreen_initial_first_default.auto_delay_unit = + title_initial_first_default.auto_delay_unit; titlescreen_first_default.fade_mode = title_first_default.fade_mode; titlescreen_first_default.fade_delay = title_first_default.fade_delay; titlescreen_first_default.post_delay = title_first_default.post_delay; titlescreen_first_default.auto_delay = title_first_default.auto_delay; + titlescreen_first_default.auto_delay_unit = + title_first_default.auto_delay_unit; titlemessage_initial_first_default.fade_mode = title_initial_first_default.fade_mode; titlemessage_initial_first_default.fade_delay = @@ -10406,27 +11081,36 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void) title_initial_first_default.post_delay; titlemessage_initial_first_default.auto_delay = title_initial_first_default.auto_delay; + titlemessage_initial_first_default.auto_delay_unit = + title_initial_first_default.auto_delay_unit; titlemessage_first_default.fade_mode = title_first_default.fade_mode; titlemessage_first_default.fade_delay = title_first_default.fade_delay; titlemessage_first_default.post_delay = title_first_default.post_delay; titlemessage_first_default.auto_delay = title_first_default.auto_delay; + titlemessage_first_default.auto_delay_unit = + title_first_default.auto_delay_unit; titlescreen_initial_default.fade_mode = title_initial_default.fade_mode; titlescreen_initial_default.fade_delay = title_initial_default.fade_delay; titlescreen_initial_default.post_delay = title_initial_default.post_delay; titlescreen_initial_default.auto_delay = title_initial_default.auto_delay; + titlescreen_initial_default.auto_delay_unit = + title_initial_default.auto_delay_unit; titlescreen_default.fade_mode = title_default.fade_mode; titlescreen_default.fade_delay = title_default.fade_delay; titlescreen_default.post_delay = title_default.post_delay; titlescreen_default.auto_delay = title_default.auto_delay; + titlescreen_default.auto_delay_unit = title_default.auto_delay_unit; titlemessage_initial_default.fade_mode = title_initial_default.fade_mode; titlemessage_initial_default.fade_delay = title_initial_default.fade_delay; titlemessage_initial_default.post_delay = title_initial_default.post_delay; - titlemessage_initial_default.auto_delay = title_initial_default.auto_delay; + titlemessage_initial_default.auto_delay_unit = + title_initial_default.auto_delay_unit; titlemessage_default.fade_mode = title_default.fade_mode; titlemessage_default.fade_delay = title_default.fade_delay; titlemessage_default.post_delay = title_default.post_delay; titlemessage_default.auto_delay = title_default.auto_delay; + titlemessage_default.auto_delay_unit = title_default.auto_delay_unit; // special case: initialize "ARG_DEFAULT" values in static default config // (e.g., init "titlemessage_1.fade_mode" from "[titlemessage].fade_mode") @@ -10815,6 +11499,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { TYPE_INTEGER, &tfi.fade_delay, ".fade_delay" }, { TYPE_INTEGER, &tfi.post_delay, ".post_delay" }, { TYPE_INTEGER, &tfi.auto_delay, ".auto_delay" }, + { TYPE_INTEGER, &tfi.auto_delay_unit, ".auto_delay_unit" }, { -1, NULL, NULL } }; @@ -10837,6 +11522,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { TYPE_INTEGER, &tmi.fade_delay, ".fade_delay" }, { TYPE_INTEGER, &tmi.post_delay, ".post_delay" }, { TYPE_INTEGER, &tmi.auto_delay, ".auto_delay" }, + { TYPE_INTEGER, &tmi.auto_delay_unit, ".auto_delay_unit" }, { -1, NULL, NULL } }; @@ -11258,19 +11944,19 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements) { if (num_unknown_tokens == 0) { - Error(ERR_INFO_LINE, "-"); - Error(ERR_INFO, "warning: unknown token(s) found in config file:"); - Error(ERR_INFO, "- config file: '%s'", filename); + Warn("---"); + Warn("unknown token(s) found in config file:"); + Warn("- config file: '%s'", filename); num_unknown_tokens++; } - Error(ERR_INFO, "- token: '%s'", list->token); + Warn("- token: '%s'", list->token); } } if (num_unknown_tokens > 0) - Error(ERR_INFO_LINE, "-"); + Warn("---"); while (*num_elements % 4) // pad with empty elements, if needed (*elements)[(*num_elements)++] = EL_EMPTY; @@ -11280,8 +11966,8 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements) #if 0 for (i = 0; i < *num_elements; i++) - printf("editor: element '%s' [%d]\n", - element_info[(*elements)[i]].token_name, (*elements)[i]); + Debug("editor", "element '%s' [%d]\n", + element_info[(*elements)[i]].token_name, (*elements)[i]); #endif } @@ -11460,7 +12146,8 @@ void LoadMusicInfo(void) if ((dir = openDirectory(music_directory)) == NULL) { - Error(ERR_WARN, "cannot read music directory '%s'", music_directory); + Warn("cannot read music directory '%s'", music_directory); + return; } @@ -11548,18 +12235,18 @@ static void print_unknown_token(char *filename, char *token, int token_nr) { if (token_nr == 0) { - Error(ERR_INFO_LINE, "-"); - Error(ERR_INFO, "warning: unknown token(s) found in config file:"); - Error(ERR_INFO, "- config file: '%s'", filename); + Warn("---"); + Warn("unknown token(s) found in config file:"); + Warn("- config file: '%s'", filename); } - Error(ERR_INFO, "- token: '%s'", token); + Warn("- token: '%s'", token); } static void print_unknown_token_end(int token_nr) { if (token_nr > 0) - Error(ERR_INFO_LINE, "-"); + Warn("---"); } void LoadHelpAnimInfo(void) @@ -11763,12 +12450,12 @@ void LoadHelpAnimInfo(void) #if 0 for (i = 0; i < num_list_entries; i++) - printf("::: '%s': %d, %d, %d => %d\n", - EL_NAME(helpanim_info[i].element), - helpanim_info[i].element, - helpanim_info[i].action, - helpanim_info[i].direction, - helpanim_info[i].delay); + Debug("files:LoadHelpAnimInfo", "'%s': %d, %d, %d => %d", + EL_NAME(helpanim_info[i].element), + helpanim_info[i].element, + helpanim_info[i].action, + helpanim_info[i].direction, + helpanim_info[i].delay); #endif } @@ -11800,8 +12487,8 @@ void LoadHelpTextInfo(void) #if 0 BEGIN_HASH_ITERATION(helptext_info, itr) { - printf("::: '%s' => '%s'\n", - HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr)); + Debug("files:LoadHelpTextInfo", "'%s' => '%s'", + HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr)); } END_HASH_ITERATION(hash, itr) #endif @@ -11827,8 +12514,7 @@ void ConvertLevels(void) global.convert_leveldir); if (convert_leveldir == NULL) - Error(ERR_EXIT, "no such level identifier: '%s'", - global.convert_leveldir); + Fail("no such level identifier: '%s'", global.convert_leveldir); leveldir_current = convert_leveldir; @@ -11955,13 +12641,13 @@ void CreateLevelSketchImages(void) BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0); if (SDL_SaveBMP(bitmap1->surface, filename1) != 0) - Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename1); + Fail("cannot save level sketch image file '%s'", filename1); DrawSizedElement(0, 0, element, MINI_TILESIZE); BlitBitmap(drawto, bitmap2, SX, SY, MINI_TILEX, MINI_TILEY, 0, 0); if (SDL_SaveBMP(bitmap2->surface, filename2) != 0) - Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename2); + Fail("cannot save level sketch image file '%s'", filename2); free(filename1); free(filename2); @@ -11987,7 +12673,7 @@ void CreateLevelSketchImages(void) if (options.debug) fprintf(stderr, "\n"); - Error(ERR_INFO, "%d normal and small images created", NUM_FILE_ELEMENTS); + Info("%d normal and small images created", NUM_FILE_ELEMENTS); CloseAllAndExit(0); } @@ -12085,7 +12771,7 @@ void CreateCustomElementImages(char *directory) } if (SDL_SaveBMP(bitmap->surface, dst_filename) != 0) - Error(ERR_EXIT, "cannot save CE graphics file '%s'", dst_filename); + Fail("cannot save CE graphics file '%s'", dst_filename); FreeBitmap(bitmap);