X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=8889f810ecaf59d121a62cbf506fb558757c4f9b;hb=HEAD;hp=133a2d2609b2515de8b5eeb3edf82d34ae65a798;hpb=088c9c2caa2434a1667dc8ea25b152b41cf7dc1b;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index 133a2d26..097deead 100644 --- a/src/files.c +++ b/src/files.c @@ -22,6 +22,7 @@ #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 +52,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 +60,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 +114,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 + \ @@ -327,6 +328,11 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), &li.finish_dig_collect, TRUE }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(17), + &li.keep_walkable_ce, FALSE + }, // (these values are different for each player) { @@ -898,11 +904,34 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = 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), @@ -1371,6 +1400,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) { { @@ -1815,6 +1864,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) @@ -1844,8 +1903,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); @@ -1888,6 +1946,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; @@ -2809,9 +2877,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)); @@ -2947,7 +3016,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)); @@ -2988,7 +3057,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; } @@ -3155,7 +3224,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, value = getMappedElement(value); if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value) = value; + *(boolean *)(conf[i].value) = (value ? TRUE : FALSE); else *(int *) (conf[i].value) = value; @@ -3317,6 +3386,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 @@ -3346,6 +3419,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 @@ -3366,6 +3442,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) @@ -3488,6 +3588,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 } }; @@ -3521,6 +3622,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 @@ -3528,6 +3637,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; } } } @@ -3601,6 +3712,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); @@ -3637,7 +3749,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) // initialize player positions and delete players from the playfield for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { - if (ELEM_IS_PLAYER(level->field[x][y])) + if (IS_PLAYER_ELEMENT(level->field[x][y])) { int player_nr = GET_PLAYER_NR(level->field[x][y]); @@ -3938,12 +4050,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++) @@ -4051,7 +4162,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); @@ -4060,9 +4171,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); @@ -4079,6 +4194,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] = @@ -4088,7 +4212,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); @@ -4097,9 +4221,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); @@ -4119,6 +4247,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]); @@ -5600,8 +5737,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; @@ -5850,7 +5986,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, } } - LoadLevelFromFileStream_DC(file, level, level_file_info->nr); + LoadLevelFromFileStream_DC(file, level); closeFile(file); } @@ -5891,6 +6027,21 @@ int getMappedElement_SB(int element_ascii, boolean use_ces) return EL_UNDEFINED; } +static void SetLevelSettings_SB(struct LevelInfo *level) +{ + // time settings + level->time = 0; + level->use_step_counter = TRUE; + + // score settings + level->score[SC_TIME_BONUS] = 0; + level->time_score_base = 1; + level->rate_time_over_score = TRUE; + + // game settings + level->auto_exit_sokoban = TRUE; +} + static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -6124,14 +6275,11 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, } // set special level settings for Sokoban levels - - level->time = 0; - level->use_step_counter = TRUE; + SetLevelSettings_SB(level); if (load_xsb_to_ces) { // special global settings can now be set in level template - level->use_custom_template = TRUE; } } @@ -6464,6 +6612,41 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0 if (level->game_version <= VERSION_IDENT(4,2,2,0)) level->finish_dig_collect = FALSE; + + // CE changing to player was kept under the player if walkable up to 4.2.3.1 + if (level->game_version <= VERSION_IDENT(4,2,3,1)) + level->keep_walkable_ce = TRUE; +} + +static void LoadLevel_InitSettings_SB(struct LevelInfo *level) +{ + boolean is_sokoban_level = TRUE; // unless non-Sokoban elements found + int x, y; + + // check if this level is (not) a Sokoban level + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + if (!IS_SB_ELEMENT(Tile[x][y])) + is_sokoban_level = FALSE; + + if (is_sokoban_level) + { + // set special level settings for Sokoban levels + SetLevelSettings_SB(level); + } +} + +static void LoadLevel_InitSettings(struct LevelInfo *level) +{ + // adjust level settings for (non-native) Sokoban-style levels + LoadLevel_InitSettings_SB(level); + + // rename levels with title "nameless level" or if renaming is forced + if (leveldir_current->empty_level_name != NULL && + (strEqual(level->name, NAMELESS_LEVEL_NAME) || + leveldir_current->force_level_name)) + snprintf(level->name, MAX_LEVEL_NAME_LEN + 1, + leveldir_current->empty_level_name, level_nr); } static void LoadLevel_InitStandardElements(struct LevelInfo *level) @@ -6611,6 +6794,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) @@ -6671,6 +6875,7 @@ static void LoadLevelTemplate_LoadAndInit(void) LoadLevel_InitVersion(&level_template); LoadLevel_InitElements(&level_template); + LoadLevel_InitSettings(&level_template); ActivateLevelTemplate(); } @@ -6711,6 +6916,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level) LoadLevel_InitVersion(&level); LoadLevel_InitElements(&level); LoadLevel_InitPlayfield(&level); + LoadLevel_InitSettings(&level); LoadLevel_InitNativeEngines(&level); } @@ -7179,7 +7385,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); @@ -7219,7 +7425,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); } } @@ -7470,6 +7676,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) { @@ -7561,6 +7783,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); @@ -7636,10 +7870,55 @@ void DumpLevel(struct LevelInfo *level) Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no")); Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no")); Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no")); + Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no")); + + if (options.debug) + { + int i, j; + + for (i = 0; i < NUM_ENVELOPES; i++) + { + char *text = level->envelope[i].text; + int text_len = strlen(text); + boolean has_text = FALSE; + + for (j = 0; j < text_len; j++) + if (text[j] != ' ' && text[j] != '\n') + has_text = TRUE; + + if (has_text) + { + Print("\n"); + Print("Envelope %d:\n'%s'\n", i + 1, text); + } + } + } PrintLine("-", 79); } +void DumpLevels(void) +{ + static LevelDirTree *dumplevel_leveldir = NULL; + + dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumplevel_leveldir); + + if (dumplevel_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumplevel_leveldir); + + if (global.dumplevel_level_nr < dumplevel_leveldir->first_level || + global.dumplevel_level_nr > dumplevel_leveldir->last_level) + Fail("no such level number: %d", global.dumplevel_level_nr); + + leveldir_current = dumplevel_leveldir; + + LoadLevel(global.dumplevel_level_nr); + DumpLevel(&level); + + CloseAllAndExit(0); +} + // ============================================================================ // tape file functions @@ -7665,6 +7944,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; @@ -7673,6 +7953,7 @@ static void setTapeInfoToDefaults(void) tape.scr_fieldx = SCR_FIELDX_DEFAULT; tape.scr_fieldy = SCR_FIELDY_DEFAULT; + tape.no_info_chunk = TRUE; tape.no_valid_file = FALSE; } @@ -7750,8 +8031,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) @@ -7777,6 +8057,8 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) int level_identifier_size; int i; + tape->no_info_chunk = FALSE; + level_identifier_size = getFile16BitBE(file); level_identifier = checked_malloc(level_identifier_size); @@ -8147,6 +8429,20 @@ void LoadSolutionTape(int nr) CopyNativeTape_SP_to_RND(&level); } +void LoadScoreTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + +void LoadScoreCacheTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreCacheTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + static boolean checkSaveTape_SCRN(struct TapeInfo *tape) { // chunk required for team mode tapes with non-default screen size @@ -8180,9 +8476,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); } @@ -8275,13 +8569,10 @@ void SaveTapeToFilename(char *filename) SetFilePermissions(filename, PERMS_PRIVATE); } -void SaveTape(int nr) +static void SaveTapeExt(char *filename) { - char *filename = getTapeFilename(nr); int i; - InitTapeDirectory(leveldir_current->subdir); - tape.file_version = FILE_VERSION_ACTUAL; tape.game_version = GAME_VERSION_ACTUAL; @@ -8297,6 +8588,25 @@ void SaveTape(int nr) 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) { @@ -8341,11 +8651,47 @@ void DumpTape(struct TapeInfo *tape) } PrintLine("-", 79); + Print("Tape of Level %03d (file version %08d, game version %08d)\n", tape->level_nr, tape->file_version, tape->game_version); Print(" (effective engine version %08d)\n", tape->engine_version); Print("Level series identifier: '%s'\n", tape->level_identifier); + + Print("Solution tape: %s\n", + tape->solved ? "yes" : + tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no"); + + Print("Special tape properties: "); + if (tape->property_bits == TAPE_PROPERTY_NONE) + Print("[none]"); + if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG) + Print("[em_random_bug]"); + if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED) + Print("[game_speed]"); + if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE) + Print("[pause]"); + if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP) + Print("[single_step]"); + if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT) + Print("[snapshot]"); + if (tape->property_bits & TAPE_PROPERTY_REPLAYED) + Print("[replayed]"); + if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS) + Print("[tas_keys]"); + if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS) + Print("[small_graphics]"); + Print("\n"); + + int year2 = tape->date / 10000; + int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); + int month_index_raw = (tape->date / 100) % 100; + int month_index = month_index_raw % 12; // prevent invalid index + int month = month_index + 1; + int day = tape->date % 100; + + Print("Tape date: %04d-%02d-%02d\n", year4, month, day); + PrintLine("-", 79); tape_frame_counter = 0; @@ -8383,21 +8729,81 @@ void DumpTape(struct TapeInfo *tape) PrintLine("-", 79); } +void DumpTapes(void) +{ + static LevelDirTree *dumptape_leveldir = NULL; + + dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumptape_leveldir); + + if (dumptape_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumptape_leveldir); + + if (global.dumptape_level_nr < dumptape_leveldir->first_level || + global.dumptape_level_nr > dumptape_leveldir->last_level) + Fail("no such level number: %d", global.dumptape_level_nr); + + leveldir_current = dumptape_leveldir; + + if (options.mytapes) + LoadTape(global.dumptape_level_nr); + else + LoadSolutionTape(global.dumptape_level_nr); + + DumpTape(&tape); + + CloseAllAndExit(0); +} + // ============================================================================ // score file functions // ============================================================================ -static void setScoreInfoToDefaults(void) +static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) { int i; for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - strcpy(scores.entry[i].name, EMPTY_PLAYER_NAME); - scores.entry[i].score = 0; - scores.entry[i].time = 0; + strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME); + strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME); + scores->entry[i].score = 0; + scores->entry[i].time = 0; + + scores->entry[i].id = -1; + strcpy(scores->entry[i].tape_date, UNKNOWN_NAME); + strcpy(scores->entry[i].platform, UNKNOWN_NAME); + strcpy(scores->entry[i].version, UNKNOWN_NAME); + strcpy(scores->entry[i].country_name, UNKNOWN_NAME); + strcpy(scores->entry[i].country_code, "??"); } + + scores->num_entries = 0; + scores->last_added = -1; + scores->last_added_local = -1; + + scores->updated = FALSE; + scores->uploaded = FALSE; + scores->tape_downloaded = FALSE; + scores->force_last_added = FALSE; + + // The following values are intentionally not reset here: + // - last_level_nr + // - last_entry_nr + // - next_level_nr + // - continue_playing + // - continue_on_return +} + +static void setScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&scores); +} + +static void setServerScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&server_scores); } static void LoadScore_OLD(int nr) @@ -8452,6 +8858,25 @@ static void LoadScore_OLD(int nr) fclose(file); } +static void ConvertScore_OLD(void) +{ + // only convert score to time for levels that rate playing time over score + if (!level.rate_time_over_score) + return; + + // convert old score to playing time for score-less levels (like Supaplex) + int time_final_max = 999; + int i; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + int score = scores.entry[i].score; + + if (score > 0 && score < time_final_max) + scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND; + } +} + static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores) { scores->file_version = getFileVersion(file); @@ -8515,21 +8940,50 @@ static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) return chunk_size; } -static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) +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].time = getFile32BitBE(file); + scores->entry[i].score = getFile32BitBE(file); chunk_size = scores->num_entries * 4; return chunk_size; } -void LoadScore(int nr) +static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) { - char *filename = getScoreFilename(nr); + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].time = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + +static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + scores->entry[i].tape_basename[j] = getFile8Bit(file); + + scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + return chunk_size; +} + +void LoadScore(int nr) +{ + char *filename = getScoreFilename(nr); char cookie[MAX_LINE_LEN]; char chunk_name[CHUNK_ID_LEN + 1]; int chunk_size; @@ -8581,6 +9035,9 @@ void LoadScore(int nr) { // 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 { @@ -8596,7 +9053,9 @@ 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 }, { NULL, 0, NULL } }; @@ -8649,7 +9108,6 @@ void LoadScore(int nr) void SaveScore_OLD(int nr) { int i; - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); char *filename = getScoreFilename(nr); FILE *file; @@ -8670,7 +9128,7 @@ void SaveScore_OLD(int nr) fclose(file); - SetFilePermissions(filename, permissions); + SetFilePermissions(filename, PERMS_PRIVATE); } #endif @@ -8715,6 +9173,14 @@ static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) putFile16BitBE(file, scores->entry[i].score); } +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); +} + static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) { int i; @@ -8723,14 +9189,30 @@ static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) 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 sc4r_chunk_size; int time_chunk_size; + int tape_chunk_size; + boolean has_large_score_values; + int i; if (!(file = fopen(filename, MODE_WRITE))) { @@ -8742,7 +9224,14 @@ static void SaveScoreToFilename(char *filename) info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2; name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN; scor_chunk_size = scores.num_entries * 2; + sc4r_chunk_size = scores.num_entries * 4; time_chunk_size = scores.num_entries * 4; + tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); @@ -8756,15 +9245,26 @@ static void SaveScoreToFilename(char *filename) putFileChunkBE(file, "NAME", name_chunk_size); SaveScore_NAME(file, &scores); - putFileChunkBE(file, "SCOR", scor_chunk_size); - SaveScore_SCOR(file, &scores); + if (has_large_score_values) + { + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); + } + else + { + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); + } putFileChunkBE(file, "TIME", time_chunk_size); SaveScore_TIME(file, &scores); + putFileChunkBE(file, "TAPE", tape_chunk_size); + SaveScore_TAPE(file, &scores); + fclose(file); - SetFilePermissions(filename, permissions); + SetFilePermissions(filename, PERMS_PRIVATE); } void SaveScore(int nr) @@ -8796,6 +9296,161 @@ void SaveScore(int nr) SaveScoreToFilename(filename); } +static void LoadServerScoreFromCache(int nr) +{ + struct ScoreEntry score_entry; + struct + { + void *value; + boolean is_string; + int string_size; + } + score_mapping[] = + { + { &score_entry.score, FALSE, 0 }, + { &score_entry.time, FALSE, 0 }, + { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN }, + { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN }, + { score_entry.tape_date, TRUE, MAX_ISO_DATE_LEN }, + { &score_entry.id, FALSE, 0 }, + { score_entry.platform, TRUE, MAX_PLATFORM_TEXT_LEN }, + { score_entry.version, TRUE, MAX_VERSION_TEXT_LEN }, + { score_entry.country_code, TRUE, MAX_COUNTRY_CODE_LEN }, + { score_entry.country_name, TRUE, MAX_COUNTRY_NAME_LEN }, + + { NULL, FALSE, 0 } + }; + char *filename = getScoreCacheFilename(nr); + SetupFileHash *score_hash = loadSetupFileHash(filename); + int i, j; + + server_scores.num_entries = 0; + + if (score_hash == NULL) + return; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + score_entry = server_scores.entry[i]; + + for (j = 0; score_mapping[j].value != NULL; j++) + { + char token[10]; + + sprintf(token, "%02d.%d", i, j); + + char *value = getHashEntry(score_hash, token); + + if (value == NULL) + continue; + + if (score_mapping[j].is_string) + { + char *score_value = (char *)score_mapping[j].value; + int value_size = score_mapping[j].string_size; + + strncpy(score_value, value, value_size); + score_value[value_size] = '\0'; + } + else + { + int *score_value = (int *)score_mapping[j].value; + + *score_value = atoi(value); + } + + server_scores.num_entries = i + 1; + } + + server_scores.entry[i] = score_entry; + } + + freeSetupFileHash(score_hash); +} + +void LoadServerScore(int nr, boolean download_score) +{ + if (!setup.use_api_server) + return; + + // always start with reliable default values + setServerScoreInfoToDefaults(); + + // 1st step: load server scores from cache file (which may not exist) + // (this should prevent reading it while the thread is writing to it) + LoadServerScoreFromCache(nr); + + if (download_score && runtime.use_api_server) + { + // 2nd step: download server scores from score server to cache file + // (as thread, as it might time out if the server is not reachable) + ApiGetScoreAsThread(nr); + } +} + +void PrepareScoreTapesForUpload(char *leveldir_subdir) +{ + MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); + + // if score tape not uploaded, ask for uploading missing tapes later + if (!setup.has_remaining_tapes) + setup.ask_for_remaining_tapes = TRUE; + + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; + + SaveSetup_ServerSetup(); +} + +void SaveServerScore(int nr, boolean tape_saved) +{ + if (!runtime.use_api_server) + { + PrepareScoreTapesForUpload(leveldir_current->subdir); + + return; + } + + ApiAddScoreAsThread(nr, tape_saved, NULL); +} + +void SaveServerScoreFromFile(int nr, boolean tape_saved, + char *score_tape_filename) +{ + if (!runtime.use_api_server) + return; + + ApiAddScoreAsThread(nr, tape_saved, score_tape_filename); +} + +void LoadLocalAndServerScore(int nr, boolean download_score) +{ + int last_added_local = scores.last_added_local; + boolean force_last_added = scores.force_last_added; + + // needed if only showing server scores + setScoreInfoToDefaults(); + + if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY)) + LoadScore(nr); + + // restore last added local score entry (before merging server scores) + scores.last_added = scores.last_added_local = last_added_local; + + if (setup.use_api_server && + !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY)) + { + // load server scores from cache file and trigger update from server + LoadServerScore(nr, download_score); + + // merge local scores with scores from server + MergeServerScore(); + } + + if (force_last_added) + scores.force_last_added = force_last_added; +} + // ============================================================================ // setup file functions @@ -8834,6 +9489,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" @@ -8862,6 +9521,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" @@ -8980,7 +9647,15 @@ static struct TokenInfo global_setup_tokens[] = }, { TYPE_SWITCH, - &setup.show_snapshot_buttons, "show_snapshot_buttons" + &setup.show_load_save_buttons, "show_load_save_buttons" + }, + { + TYPE_SWITCH, + &setup.show_undo_redo_buttons, "show_undo_redo_buttons" + }, + { + TYPE_STRING, + &setup.scores_in_highscore_list, "scores_in_highscore_list" }, { TYPE_STRING, @@ -9070,6 +9745,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[] = @@ -9080,6 +9759,50 @@ static struct TokenInfo auto_setup_tokens[] = }, }; +static struct TokenInfo server_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_uuid, "player_uuid" + }, + { + TYPE_INTEGER, + &setup.player_version, "player_version" + }, + { + TYPE_SWITCH, + &setup.use_api_server, TEST_PREFIX "use_api_server" + }, + { + TYPE_STRING, + &setup.api_server_hostname, TEST_PREFIX "api_server_hostname" + }, + { + TYPE_STRING, + &setup.api_server_password, TEST_PREFIX "api_server_password" + }, + { + TYPE_SWITCH, + &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_remaining_tapes, TEST_PREFIX "ask_for_remaining_tapes" + }, + { + TYPE_SWITCH, + &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_using_api_server,TEST_PREFIX "ask_for_using_api_server" + }, + { + TYPE_SWITCH, + &setup.has_remaining_tapes, TEST_PREFIX "has_remaining_tapes" + }, +}; + static struct TokenInfo editor_setup_tokens[] = { { @@ -9170,6 +9893,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" @@ -9194,6 +9921,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" @@ -9443,10 +10178,18 @@ static struct TokenInfo internal_setup_tokens[] = 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" @@ -9483,6 +10226,58 @@ static struct TokenInfo internal_setup_tokens[] = TYPE_BOOLEAN, &setup.internal.menu_save_and_exit, "menu_save_and_exit" }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_various, "menu_shortcuts_various" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_focus, "menu_shortcuts_focus" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_tape, "menu_shortcuts_tape" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_title, "info_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_elements, "info_elements" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_music, "info_music" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_credits, "info_credits" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_program, "info_program" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_version, "info_version" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_levelset, "info_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_exit, "info_exit" + }, }; static struct TokenInfo debug_setup_tokens[] = @@ -9594,6 +10389,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) @@ -9609,6 +10412,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; @@ -9616,6 +10420,8 @@ 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; @@ -9645,7 +10451,9 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->game_frame_delay = GAME_FRAME_DELAY; si->sp_show_border_elements = FALSE; si->small_game_graphics = FALSE; - si->show_snapshot_buttons = FALSE; + si->show_load_save_buttons = FALSE; + si->show_undo_redo_buttons = FALSE; + si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT); si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); @@ -9719,6 +10527,8 @@ 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_emerald_mine = TRUE; si->editor.el_emerald_mine_club = TRUE; @@ -9750,6 +10560,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; @@ -9821,6 +10633,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; @@ -9856,9 +10669,12 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) 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); @@ -9869,6 +10685,21 @@ static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE; } +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; @@ -9887,6 +10718,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; @@ -9956,7 +10788,7 @@ static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, token_info[token_nr].text); } -static void decodeSetupFileHash(SetupFileHash *setup_file_hash) +static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash) { int i, pnr; @@ -10056,6 +10888,19 @@ static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash) auto_setup_tokens[i].text)); } +static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash) +{ + int i; + + if (!setup_file_hash) + return; + + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + setSetupInfo(server_setup_tokens, i, + getHashEntry(setup_file_hash, + server_setup_tokens[i].text)); +} + static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) { int i; @@ -10112,7 +10957,7 @@ void LoadSetupFromFilename(char *filename) if (setup_file_hash) { - decodeSetupFileHash(setup_file_hash); + decodeSetupFileHash_Default(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -10143,7 +10988,7 @@ static void LoadSetup_SpecialPostProcessing(void) MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY); } -void LoadSetup(void) +void LoadSetup_Default(void) { char *filename; @@ -10153,6 +10998,12 @@ void LoadSetup(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); @@ -10184,6 +11035,35 @@ void LoadSetup_AutoSetup(void) free(filename); } +void LoadSetup_ServerSetup(void) +{ + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + SetupFileHash *setup_file_hash = NULL; + + // always start with reliable default values + setSetupInfoToDefaults_ServerSetup(&setup); + + setup_file_hash = loadSetupFileHash(filename); + + if (setup_file_hash) + { + decodeSetupFileHash_ServerSetup(setup_file_hash); + + freeSetupFileHash(setup_file_hash); + } + + free(filename); + + if (setup.player_uuid == NULL) + { + // player UUID does not yet exist in setup file + setup.player_uuid = getStringCopy(getUUID()); + setup.player_version = 2; + + SaveSetup_ServerSetup(); + } +} + void LoadSetup_EditorCascade(void) { char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); @@ -10204,6 +11084,14 @@ void LoadSetup_EditorCascade(void) free(filename); } +void LoadSetup(void) +{ + LoadSetup_Default(); + LoadSetup_AutoSetup(); + LoadSetup_ServerSetup(); + LoadSetup_EditorCascade(); +} + static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, char *mapping_line) { @@ -10253,7 +11141,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, fclose(file); } -void SaveSetup(void) +void SaveSetup_Default(void) { char *filename = getSetupFilename(); FILE *file; @@ -10346,18 +11234,47 @@ void SaveSetup(void) setup.debug.xsn_mode != AUTO) fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); - fprintf(file, "\n"); - for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) - fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} + +void SaveSetup_AutoSetup(void) +{ + char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + FILE *file; + int i; + + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write auto setup file '%s'", filename); + + free(filename); + + return; + } + + fprintFileHeader(file, AUTOSETUP_FILENAME); + + for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); fclose(file); SetFilePermissions(filename, PERMS_PRIVATE); + + free(filename); } -void SaveSetup_AutoSetup(void) +void SaveSetup_ServerSetup(void) { - char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); FILE *file; int i; @@ -10365,17 +11282,23 @@ void SaveSetup_AutoSetup(void) if (!(file = fopen(filename, MODE_WRITE))) { - Warn("cannot write auto setup file '%s'", filename); + Warn("cannot write server setup file '%s'", filename); free(filename); return; } - fprintFileHeader(file, AUTOSETUP_FILENAME); + fprintFileHeader(file, SERVERSETUP_FILENAME); - for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) - fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + { + // just to make things nicer :) + if (server_setup_tokens[i].value == &setup.use_api_server) + fprintf(file, "\n"); + + fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i)); + } fclose(file); @@ -10413,6 +11336,14 @@ void SaveSetup_EditorCascade(void) free(filename); } +void SaveSetup(void) +{ + SaveSetup_Default(); + SaveSetup_AutoSetup(); + SaveSetup_ServerSetup(); + SaveSetup_EditorCascade(); +} + static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, char *filename) { @@ -10600,8 +11531,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 @@ -10619,6 +11551,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[] = @@ -10644,6 +11655,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]); @@ -10773,6 +11789,18 @@ static int get_anim_action_parameter_value(char *token) result = -(int)key; } + if (result == -1) + { + if (isURL(token)) + { + result = get_hash_from_key(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; @@ -10801,6 +11829,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")) @@ -10825,6 +11855,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 : @@ -10832,6 +11863,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")) @@ -10888,11 +11921,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 : @@ -10939,14 +11977,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 = @@ -10954,6 +11996,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; @@ -11159,7 +12207,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 @@ -11394,6 +12442,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; @@ -11528,7 +12615,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++) @@ -11568,6 +12657,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] }, @@ -11732,35 +12822,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); } @@ -11771,6 +12837,7 @@ void LoadMenuDesignSettings(void) InitMenuDesignSettings_Static(); InitMenuDesignSettings_SpecialPreProcessing(); + InitMenuDesignSettings_PreviewPlayers(); if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS)) { @@ -11893,11 +12960,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 }, }; @@ -11993,14 +13062,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) @@ -12013,11 +13080,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); @@ -12026,76 +13095,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); @@ -12117,6 +13178,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, @@ -12462,6 +13535,11 @@ void ConvertLevels(void) Print("converting level ... "); +#if 0 + // special case: conversion of some EMC levels as requested by ACME + level.game_engine_type = GAME_ENGINE_TYPE_RND; +#endif + level_filename = getDefaultLevelFilename(level_nr); new_level = !fileExists(level_filename); @@ -12539,8 +13617,8 @@ void CreateLevelSketchImages(void) sprintf(basename1, "%04d.bmp", i); sprintf(basename2, "%04ds.bmp", i); - filename1 = getPath2(global.create_images_dir, basename1); - filename2 = getPath2(global.create_images_dir, basename2); + filename1 = getPath2(global.create_sketch_images_dir, basename1); + filename2 = getPath2(global.create_sketch_images_dir, basename2); DrawSizedElement(0, 0, element, TILESIZE); BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0); @@ -12584,6 +13662,135 @@ void CreateLevelSketchImages(void) } +// ---------------------------------------------------------------------------- +// create and save images for element collecting animations (raw BMP format) +// ---------------------------------------------------------------------------- + +static boolean createCollectImage(int element) +{ + return (IS_COLLECTIBLE(element) && !IS_SP_ELEMENT(element)); +} + +void CreateCollectElementImages(void) +{ + int i, j; + int num_steps = 8; + int anim_frames = num_steps - 1; + int tile_size = TILESIZE; + int anim_width = tile_size * anim_frames; + int anim_height = tile_size; + int num_collect_images = 0; + int pos_collect_images = 0; + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + if (createCollectImage(i)) + num_collect_images++; + + Info("Creating %d element collecting animation images ...", + num_collect_images); + + int dst_width = anim_width * 2; + int dst_height = anim_height * num_collect_images / 2; + Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH); + char *basename_bmp = "RocksCollect.bmp"; + char *basename_png = "RocksCollect.png"; + char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp); + char *filename_png = getPath2(global.create_collect_images_dir, basename_png); + int len_filename_bmp = strlen(filename_bmp); + int len_filename_png = strlen(filename_png); + int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png; + char cmd_convert[max_command_len]; + + snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"", + filename_bmp, + filename_png); + + // force using RGBA surface for destination bitmap + SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00)); + + dst_bitmap->surface = + SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + if (!createCollectImage(i)) + continue; + + int dst_x = (pos_collect_images / (num_collect_images / 2)) * anim_width; + int dst_y = (pos_collect_images % (num_collect_images / 2)) * anim_height; + int graphic = el2img(i); + char *token_name = element_info[i].token_name; + Bitmap *tmp_bitmap = CreateBitmap(tile_size, tile_size, DEFAULT_DEPTH); + Bitmap *src_bitmap; + int src_x, src_y; + + Info("- creating collecting image for '%s' ...", token_name); + + getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y); + + BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y, + tile_size, tile_size, 0, 0); + + // force using RGBA surface for temporary bitmap (using transparent black) + SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00)); + + tmp_bitmap->surface = + SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + + tmp_bitmap->surface_masked = tmp_bitmap->surface; + + for (j = 0; j < anim_frames; j++) + { + int frame_size_final = tile_size * (anim_frames - j) / num_steps; + int frame_size = frame_size_final * num_steps; + int offset = (tile_size - frame_size_final) / 2; + Bitmap *frame_bitmap = ZoomBitmap(tmp_bitmap, frame_size, frame_size); + + while (frame_size > frame_size_final) + { + frame_size /= 2; + + Bitmap *half_bitmap = ZoomBitmap(frame_bitmap, frame_size, frame_size); + + FreeBitmap(frame_bitmap); + + frame_bitmap = half_bitmap; + } + + BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0, + frame_size_final, frame_size_final, + dst_x + j * tile_size + offset, dst_y + offset); + + FreeBitmap(frame_bitmap); + } + + tmp_bitmap->surface_masked = NULL; + + FreeBitmap(tmp_bitmap); + + pos_collect_images++; + } + + if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0) + Fail("cannot save element collecting image file '%s'", filename_bmp); + + FreeBitmap(dst_bitmap); + + Info("Converting image file from BMP to PNG ..."); + + if (system(cmd_convert) != 0) + Fail("converting image file failed"); + + unlink(filename_bmp); + + Info("Done."); + + CloseAllAndExit(0); +} + + // ---------------------------------------------------------------------------- // create and save images for custom and group elements (raw BMP format) // ----------------------------------------------------------------------------