X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=blobdiff_plain;f=src%2Flibgame%2Fsetup.c;h=b419875a580e8fd1f87eab476ec4b496f232db7c;hp=5a1b6c7d310f2d00cb07ec954460c9c8c47929fc;hb=HEAD;hpb=36d5cb36c6f4cc188aecd157b48ff064be69afb6 diff --git a/src/libgame/setup.c b/src/libgame/setup.c index 5a1b6c7d..b419875a 100644 --- a/src/libgame/setup.c +++ b/src/libgame/setup.c @@ -19,6 +19,7 @@ #include "platform.h" #include "setup.h" +#include "sound.h" #include "joystick.h" #include "text.h" #include "misc.h" @@ -43,46 +44,19 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] = "DX Boulderdash" }; - -#define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \ - IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \ - IS_LEVELCLASS_BD(n) ? FC_YELLOW : \ - IS_LEVELCLASS_EM(n) ? FC_YELLOW : \ - IS_LEVELCLASS_SP(n) ? FC_YELLOW : \ - IS_LEVELCLASS_DX(n) ? FC_YELLOW : \ - IS_LEVELCLASS_SB(n) ? FC_YELLOW : \ - IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \ - IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \ - FC_BLUE) - -#define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \ - IS_LEVELCLASS_CLASSICS(n) ? 1 : \ - IS_LEVELCLASS_BD(n) ? 2 : \ - IS_LEVELCLASS_EM(n) ? 3 : \ - IS_LEVELCLASS_SP(n) ? 4 : \ - IS_LEVELCLASS_DX(n) ? 5 : \ - IS_LEVELCLASS_SB(n) ? 6 : \ - IS_LEVELCLASS_CONTRIB(n) ? 7 : \ - IS_LEVELCLASS_PRIVATE(n) ? 8 : \ - 9) - -#define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \ - IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \ - IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \ - IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \ - FC_BLUE) - -#define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \ - IS_ARTWORKCLASS_LEVEL(n) ? 1 : \ - IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \ - IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \ - 9) - #define TOKEN_VALUE_POSITION_SHORT 32 #define TOKEN_VALUE_POSITION_DEFAULT 40 #define TOKEN_COMMENT_POSITION_DEFAULT 60 -#define MAX_COOKIE_LEN 256 +#define TREE_NODE_TYPE_DEFAULT 0 +#define TREE_NODE_TYPE_PARENT 1 +#define TREE_NODE_TYPE_GROUP 2 +#define TREE_NODE_TYPE_COPY 3 + +#define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \ + ti->parent_link ? TREE_NODE_TYPE_PARENT : \ + ti->is_copy ? TREE_NODE_TYPE_COPY : \ + TREE_NODE_TYPE_DEFAULT) static void setTreeInfoToDefaults(TreeInfo *, int); @@ -95,13 +69,25 @@ static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT; static SetupFileHash *artworkinfo_cache_old = NULL; static SetupFileHash *artworkinfo_cache_new = NULL; static SetupFileHash *optional_tokens_hash = NULL; +static SetupFileHash *missing_file_hash = NULL; static boolean use_artworkinfo_cache = TRUE; +static boolean update_artworkinfo_cache = FALSE; // ---------------------------------------------------------------------------- // file functions // ---------------------------------------------------------------------------- +static void WarnUsingFallback(char *filename) +{ + if (getHashEntry(missing_file_hash, filename) == NULL) + { + setHashEntry(missing_file_hash, filename, ""); + + Debug("setup", "cannot find artwork file '%s' (using fallback)", filename); + } +} + static char *getLevelClassDescription(TreeInfo *ti) { int position = ti->sort_priority / 100; @@ -112,6 +98,16 @@ static char *getLevelClassDescription(TreeInfo *ti) return "Unknown Level Class"; } +static char *getCacheDir(void) +{ + static char *cache_dir = NULL; + + if (cache_dir == NULL) + cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY); + + return cache_dir; +} + static char *getScoreDir(char *level_subdir) { static char *score_dir = NULL; @@ -119,13 +115,29 @@ static char *getScoreDir(char *level_subdir) char *score_subdir = SCORES_DIRECTORY; if (score_dir == NULL) + score_dir = getPath2(getMainUserGameDataDir(), score_subdir); + + if (level_subdir != NULL) { - if (program.global_scores) - score_dir = getPath2(getCommonDataDir(), score_subdir); - else - score_dir = getPath2(getMainUserGameDataDir(), score_subdir); + checked_free(score_level_dir); + + score_level_dir = getPath2(score_dir, level_subdir); + + return score_level_dir; } + return score_dir; +} + +static char *getScoreCacheDir(char *level_subdir) +{ + static char *score_dir = NULL; + static char *score_level_dir = NULL; + char *score_subdir = SCORES_DIRECTORY; + + if (score_dir == NULL) + score_dir = getPath2(getCacheDir(), score_subdir); + if (level_subdir != NULL) { checked_free(score_level_dir); @@ -138,6 +150,32 @@ static char *getScoreDir(char *level_subdir) return score_dir; } +static char *getScoreTapeDir(char *level_subdir, int nr) +{ + static char *score_tape_dir = NULL; + char tape_subdir[MAX_FILENAME_LEN]; + + checked_free(score_tape_dir); + + sprintf(tape_subdir, "%03d", nr); + score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir); + + return score_tape_dir; +} + +static char *getScoreCacheTapeDir(char *level_subdir, int nr) +{ + static char *score_cache_tape_dir = NULL; + char tape_subdir[MAX_FILENAME_LEN]; + + checked_free(score_cache_tape_dir); + + sprintf(tape_subdir, "%03d", nr); + score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir); + + return score_cache_tape_dir; +} + static char *getUserSubdir(int nr) { static char user_subdir[16] = { 0 }; @@ -180,16 +218,6 @@ static char *getLevelSetupDir(char *level_subdir) return levelsetup_dir; } -static char *getCacheDir(void) -{ - static char *cache_dir = NULL; - - if (cache_dir == NULL) - cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY); - - return cache_dir; -} - static char *getNetworkDir(void) { static char *network_dir = NULL; @@ -274,7 +302,7 @@ char *getNewUserLevelSubdir(void) return new_level_subdir; } -static char *getTapeDir(char *level_subdir) +char *getTapeDir(char *level_subdir) { static char *tape_dir = NULL; char *data_dir = getUserGameDataDir(); @@ -487,7 +515,7 @@ char *getProgramMainDataPath(char *command_filename, char *base_path) set the current working directory to the program package directory) */ char *main_data_path = getBasePath(command_filename); -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR)) { char *main_data_path_old = main_data_path; @@ -529,8 +557,8 @@ char *getProgramConfigFilename(char *command_filename) if (strSuffix(command_filename_1, ".exe")) command_filename_1[strlen(command_filename_1) - 4] = '\0'; - char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH); - char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY); + char *base_path = getProgramMainDataPath(command_filename, BASE_PATH); + char *conf_directory = getPath2(base_path, CONF_DIRECTORY); char *command_basepath = getBasePath(command_filename); char *command_basename = getBaseNameNoSuffix(command_filename); @@ -540,7 +568,7 @@ char *getProgramConfigFilename(char *command_filename) config_filename_2 = getStringCat2(command_filename_2, ".conf"); config_filename_3 = getPath2(conf_directory, SETUP_FILENAME); - checked_free(ro_base_path); + checked_free(base_path); checked_free(conf_directory); checked_free(command_basepath); @@ -564,6 +592,34 @@ char *getProgramConfigFilename(char *command_filename) return config_filename_3; } +static char *getPlatformConfigFilename(char *config_filename) +{ + static char *platform_config_filename = NULL; + static boolean initialized = FALSE; + + if (!initialized) + { + char *config_basepath = getBasePath(config_filename); + char *config_basename = getBaseNameNoSuffix(config_filename); + char *config_filename_prefix = getPath2(config_basepath, config_basename); + char *platform_string_lower = getStringToLower(PLATFORM_STRING); + char *platform_suffix = getStringCat2("-", platform_string_lower); + + platform_config_filename = getStringCat3(config_filename_prefix, + platform_suffix, ".conf"); + + checked_free(config_basepath); + checked_free(config_basename); + checked_free(config_filename_prefix); + checked_free(platform_string_lower); + checked_free(platform_suffix); + + initialized = TRUE; + } + + return platform_config_filename; +} + char *getTapeFilename(int nr) { static char *filename = NULL; @@ -577,7 +633,20 @@ char *getTapeFilename(int nr) return filename; } -char *getSolutionTapeFilename(int nr) +char *getTemporaryTapeFilename(void) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION); + filename = getPath2(getTapeDir(NULL), basename); + + return filename; +} + +char *getDefaultSolutionTapeFilename(int nr) { static char *filename = NULL; char basename[MAX_FILENAME_LEN]; @@ -587,17 +656,32 @@ char *getSolutionTapeFilename(int nr) sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION); filename = getPath2(getSolutionTapeDir(), basename); - if (!fileExists(filename)) - { - static char *filename_sln = NULL; + return filename; +} + +char *getSokobanSolutionTapeFilename(int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%03d.sln", nr); + filename = getPath2(getSolutionTapeDir(), basename); + + return filename; +} - checked_free(filename_sln); +char *getSolutionTapeFilename(int nr) +{ + char *filename = getDefaultSolutionTapeFilename(nr); - sprintf(basename, "%03d.sln", nr); - filename_sln = getPath2(getSolutionTapeDir(), basename); + if (!fileExists(filename)) + { + char *filename2 = getSokobanSolutionTapeFilename(nr); - if (fileExists(filename_sln)) - return filename_sln; + if (fileExists(filename2)) + return filename2; } return filename; @@ -618,6 +702,64 @@ char *getScoreFilename(int nr) return filename; } +char *getScoreCacheFilename(int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreCacheDir(levelset.identifier), basename); + + return filename; +} + +char *getScoreTapeBasename(char *name) +{ + static char basename[MAX_FILENAME_LEN]; + char basename_raw[MAX_FILENAME_LEN]; + char timestamp[20]; + + sprintf(timestamp, "%s", getCurrentTimestamp()); + sprintf(basename_raw, "%s-%s", timestamp, name); + sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw)); + + return basename; +} + +char *getScoreTapeFilename(char *basename_no_ext, int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename); + + return filename; +} + +char *getScoreCacheTapeFilename(char *basename_no_ext, int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename); + + return filename; +} + char *getSetupFilename(void) { static char *filename = NULL; @@ -634,6 +776,11 @@ char *getDefaultSetupFilename(void) return program.config_filename; } +char *getPlatformSetupFilename(void) +{ + return getPlatformConfigFilename(program.config_filename); +} + char *getEditorSetupFilename(void) { static char *filename = NULL; @@ -672,9 +819,34 @@ char *getHelpTextFilename(void) return filename; } -char *getLevelSetInfoFilename(void) +static char *getLevelSetInfoBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "levelset_%d.txt", nr + 1); + + return basename; +} + +char *getLevelSetInfoFilename(int nr) { + char *basename = getLevelSetInfoBasename(nr); + static char *info_subdir = NULL; static char *filename = NULL; + + if (info_subdir == NULL) + info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY); + + checked_free(filename); + + // look for level set info file the current level set directory + filename = getPath3(getCurrentLevelDir(), info_subdir, basename); + if (fileExists(filename)) + return filename; + + if (nr > 0) + return NULL; + char *basenames[] = { "README", @@ -777,6 +949,65 @@ char *getLevelSetTitleMessageFilename(int nr, boolean initial) return NULL; // cannot find specified artwork file anywhere } +static char *getCreditsBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "credits_%d.txt", nr + 1); + + return basename; +} + +char *getCreditsFilename(int nr, boolean global) +{ + char *basename = getCreditsBasename(nr); + char *basepath = NULL; + static char *credits_subdir = NULL; + static char *filename = NULL; + + if (credits_subdir == NULL) + credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY); + + checked_free(filename); + + // look for credits file in the game's base or current level set directory + basepath = (global ? options.base_directory : getCurrentLevelDir()); + + filename = getPath3(basepath, credits_subdir, basename); + if (fileExists(filename)) + return filename; + + return NULL; // cannot find credits file +} + +static char *getProgramInfoBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "program_%d.txt", nr + 1); + + return basename; +} + +char *getProgramInfoFilename(int nr) +{ + char *basename = getProgramInfoBasename(nr); + static char *info_subdir = NULL; + static char *filename = NULL; + + if (info_subdir == NULL) + info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY); + + checked_free(filename); + + // look for program info file in the game's base directory + filename = getPath3(options.base_directory, info_subdir, basename); + if (fileExists(filename)) + return filename; + + return NULL; // cannot find program info file +} + static char *getCorrectedArtworkBasename(char *basename) { return basename; @@ -841,7 +1072,7 @@ char *getCustomImageFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -912,7 +1143,7 @@ char *getCustomSoundFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -983,7 +1214,7 @@ char *getCustomMusicFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -1023,7 +1254,58 @@ char *getCustomArtworkLevelConfigFilename(int type) return filename; } -char *getCustomMusicDirectory(void) +static boolean directoryExists_CheckMusic(char *directory, boolean check_music) +{ + if (!directoryExists(directory)) + return FALSE; + + if (!check_music) + return TRUE; + + Directory *dir; + DirectoryEntry *dir_entry; + int num_music = getMusicListSize(); + boolean music_found = FALSE; + + if ((dir = openDirectory(directory)) == NULL) + return FALSE; + + while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries + { + 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++) + { + struct FileInfo *music = getMusicListEntry(i); + + if (strEqual(basename, music->filename)) + { + music_already_used = TRUE; + + break; + } + } + + if (music_already_used) + continue; + + if (FileIsMusic(dir_entry->filename)) + { + music_found = TRUE; + + break; + } + } + + closeDirectory(dir); + + return music_found; +} + +static char *getCustomMusicDirectoryExt(boolean check_music) { static char *directory = NULL; boolean skip_setup_artwork = FALSE; @@ -1034,7 +1316,7 @@ char *getCustomMusicDirectory(void) { // 1st try: look for special artwork in current level series directory directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; free(directory); @@ -1044,7 +1326,9 @@ char *getCustomMusicDirectory(void) { // 2nd try: look for special artwork configured in level series config directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR)); - if (directoryExists(directory)) + + // directory also valid if no unconfigured music found (no game music) + if (directoryExists_CheckMusic(directory, FALSE)) return directory; free(directory); @@ -1058,7 +1342,9 @@ char *getCustomMusicDirectory(void) { // 3rd try: look for special artwork in configured artwork directory directory = getStringCopy(getSetupArtworkDir(artwork.mus_current)); - if (directoryExists(directory)) + + // directory also valid if no unconfigured music found (no game music) + if (directoryExists_CheckMusic(directory, FALSE)) return directory; free(directory); @@ -1066,37 +1352,104 @@ char *getCustomMusicDirectory(void) // 4th try: look for default artwork in new default artwork directory directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR)); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; free(directory); // 5th try: look for default artwork in old default artwork directory directory = getStringCopy(options.music_directory); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; return NULL; // cannot find specified artwork file anywhere } +char *getCustomMusicDirectory(void) +{ + return getCustomMusicDirectoryExt(FALSE); +} + +char *getCustomMusicDirectory_NoConf(void) +{ + return getCustomMusicDirectoryExt(TRUE); +} + +void MarkTapeDirectoryUploadsAsComplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + + touchFile(filename); + + checked_free(filename); +} + +void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + + unlink(filename); + + checked_free(filename); +} + +boolean CheckTapeDirectoryUploadsComplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + boolean success = fileExists(filename); + + checked_free(filename); + + return success; +} + +void InitMissingFileHash(void) +{ + if (missing_file_hash == NULL) + freeSetupFileHash(missing_file_hash); + + missing_file_hash = newSetupFileHash(); +} + void InitTapeDirectory(char *level_subdir) { - createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE); - createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE); + boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir)); + + createDirectory(getUserGameDataDir(), "user data"); + createDirectory(getTapeDir(NULL), "main tape"); + createDirectory(getTapeDir(level_subdir), "level tape"); + + if (new_tape_dir) + MarkTapeDirectoryUploadsAsComplete(level_subdir); } void InitScoreDirectory(char *level_subdir) { - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getScoreDir(NULL), "main score"); + createDirectory(getScoreDir(level_subdir), "level score"); +} - if (program.global_scores) - createDirectory(getCommonDataDir(), "common data", permissions); - else - createDirectory(getMainUserGameDataDir(), "main user data", permissions); +void InitScoreCacheDirectory(char *level_subdir) +{ + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getCacheDir(), "cache data"); + createDirectory(getScoreCacheDir(NULL), "main score"); + createDirectory(getScoreCacheDir(level_subdir), "level score"); +} + +void InitScoreTapeDirectory(char *level_subdir, int nr) +{ + InitScoreDirectory(level_subdir); + + createDirectory(getScoreTapeDir(level_subdir, nr), "score tape"); +} + +void InitScoreCacheTapeDirectory(char *level_subdir, int nr) +{ + InitScoreCacheDirectory(level_subdir); - createDirectory(getScoreDir(NULL), "main score", permissions); - createDirectory(getScoreDir(level_subdir), "level score", permissions); + createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape"); } static void SaveUserLevelInfo(void); @@ -1105,12 +1458,15 @@ void InitUserLevelDirectory(char *level_subdir) { if (!directoryExists(getUserLevelDir(level_subdir))) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE); - createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getUserLevelDir(NULL), "main user level"); if (setup.internal.create_user_levelset) + { + createDirectory(getUserLevelDir(level_subdir), "user level"); + SaveUserLevelInfo(); + } } } @@ -1118,24 +1474,24 @@ void InitNetworkLevelDirectory(char *level_subdir) { if (!directoryExists(getNetworkLevelDir(level_subdir))) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE); - createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE); - createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getNetworkDir(), "network data"); + createDirectory(getNetworkLevelDir(NULL), "main network level"); + createDirectory(getNetworkLevelDir(level_subdir), "network level"); } } void InitLevelSetupDirectory(char *level_subdir) { - createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE); + createDirectory(getUserGameDataDir(), "user data"); + createDirectory(getLevelSetupDir(NULL), "main level setup"); + createDirectory(getLevelSetupDir(level_subdir), "level setup"); } static void InitCacheDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getCacheDir(), "cache data"); } @@ -1163,6 +1519,16 @@ void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new) *node_first = node_new; } +void removeTreeInfo(TreeInfo **node_first) +{ + TreeInfo *node_old = *node_first; + + *node_first = node_old->next; + node_old->next = NULL; + + freeTreeInfo(node_old); +} + int numTreeInfo(TreeInfo *node) { int num = 0; @@ -1178,25 +1544,52 @@ int numTreeInfo(TreeInfo *node) boolean validLevelSeries(TreeInfo *node) { - return (node != NULL && !node->node_group && !node->parent_link); + // in a number of cases, tree node is no valid level set + if (node == NULL || node->node_group || node->parent_link || node->is_copy) + return FALSE; + + return TRUE; +} + +TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node) +{ + if (validLevelSeries(node)) + return node; + else if (node->is_copy) + return getTreeInfoFromIdentifier(leveldir_first, node->identifier); + else + return getFirstValidTreeInfoEntry(default_node); } -TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node) +static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node) { if (node == NULL) return NULL; - if (node->node_group) // enter level group (step down into tree) + if (node->node_group) // enter node group (step down into tree) return getFirstValidTreeInfoEntry(node->node_group); - else if (node->parent_link) // skip start entry of level group - { - if (node->next) // get first real level series entry - return getFirstValidTreeInfoEntry(node->next); - else // leave empty level group and go on - return getFirstValidTreeInfoEntry(node->node_parent->next); - } - else // this seems to be a regular level series + + if (node->parent_link) // skip first node (back link) of node group + get_next_node = TRUE; + + if (!get_next_node) // get current regular tree node return node; + + // get next regular tree node, or step up until one is found + while (node->next == NULL && node->node_parent != NULL) + node = node->node_parent; + + return getFirstValidTreeInfoEntry(node->next); +} + +TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node) +{ + return getValidTreeInfoEntryExt(node, FALSE); +} + +TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node) +{ + return getValidTreeInfoEntryExt(node, TRUE); } TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node) @@ -1250,29 +1643,25 @@ TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos) } static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier, - boolean include_node_groups) + int node_type_wanted) { if (identifier == NULL) return NULL; while (node) { + if (TREE_NODE_TYPE(node) == node_type_wanted && + strEqual(identifier, node->identifier)) + return node; + if (node->node_group) { - if (include_node_groups && strEqual(identifier, node->identifier)) - return node; - TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group, identifier, - include_node_groups); + node_type_wanted); if (node_group) return node_group; } - else if (!node->parent_link) - { - if (strEqual(identifier, node->identifier)) - return node; - } node = node->next; } @@ -1282,7 +1671,7 @@ static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier, TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier) { - return getTreeInfoFromIdentifierExt(node, identifier, FALSE); + return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT); } static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent, @@ -1320,30 +1709,56 @@ static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets) *ti_new = ti_cloned; } -static boolean adjustTreeGraphicsForEMC(TreeInfo *node) +static boolean adjustTreeArtworkForEMC(char **artwork_set_1, + char **artwork_set_2, + char **artwork_set, boolean prefer_2) { - boolean settings_changed = FALSE; + // do nothing if neither special artwork set 1 nor 2 are defined + if (!*artwork_set_1 && !*artwork_set_2) + return FALSE; - while (node) + boolean want_1 = (prefer_2 == FALSE); + boolean want_2 = (prefer_2 == TRUE); + boolean has_only_1 = (!*artwork_set && !*artwork_set_2); + boolean has_only_2 = (!*artwork_set && !*artwork_set_1); + char *artwork_set_new = NULL; + + // replace missing special artwork 1 or 2 with (optional) standard artwork + + if (!*artwork_set_1) + setString(artwork_set_1, *artwork_set); + + if (!*artwork_set_2) + setString(artwork_set_2, *artwork_set); + + // set standard artwork to either special artwork 1 or 2, as requested + + if (*artwork_set_1 && (want_1 || has_only_1)) + artwork_set_new = *artwork_set_1; + + if (*artwork_set_2 && (want_2 || has_only_2)) + artwork_set_new = *artwork_set_2; + + if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new)) { - boolean want_ecs = (setup.prefer_aga_graphics == FALSE); - boolean want_aga = (setup.prefer_aga_graphics == TRUE); - boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga); - boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs); - char *graphics_set = NULL; + setString(artwork_set, artwork_set_new); - if (node->graphics_set_ecs && (want_ecs || has_only_ecs)) - graphics_set = node->graphics_set_ecs; + return TRUE; + } - if (node->graphics_set_aga && (want_aga || has_only_aga)) - graphics_set = node->graphics_set_aga; + return FALSE; +} - if (graphics_set && !strEqual(node->graphics_set, graphics_set)) - { - setString(&node->graphics_set, graphics_set); - settings_changed = TRUE; - } +static boolean adjustTreeGraphicsForEMC(TreeInfo *node) +{ + boolean settings_changed = FALSE; + while (node) + { + settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs, + &node->graphics_set_aga, + &node->graphics_set, + setup.prefer_aga_graphics); if (node->node_group != NULL) settings_changed |= adjustTreeGraphicsForEMC(node->node_group); @@ -1359,24 +1774,10 @@ static boolean adjustTreeSoundsForEMC(TreeInfo *node) while (node) { - boolean want_default = (setup.prefer_lowpass_sounds == FALSE); - boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE); - boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass); - boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default); - char *sounds_set = NULL; - - if (node->sounds_set_default && (want_default || has_only_default)) - sounds_set = node->sounds_set_default; - - if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass)) - sounds_set = node->sounds_set_lowpass; - - if (sounds_set && !strEqual(node->sounds_set, sounds_set)) - { - setString(&node->sounds_set, sounds_set); - settings_changed = TRUE; - } - + settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default, + &node->sounds_set_lowpass, + &node->sounds_set, + setup.prefer_lowpass_sounds); if (node->node_group != NULL) settings_changed |= adjustTreeSoundsForEMC(node->node_group); @@ -1386,9 +1787,10 @@ static boolean adjustTreeSoundsForEMC(TreeInfo *node) return settings_changed; } -void dumpTreeInfo(TreeInfo *node, int depth) +int dumpTreeInfo(TreeInfo *node, int depth) { char bullet_list[] = { '-', '*', 'o' }; + int num_leaf_nodes = 0; int i; if (depth == 0) @@ -1404,7 +1806,11 @@ void dumpTreeInfo(TreeInfo *node, int depth) DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n", bullet, node->name, node->identifier, (node->node_parent ? node->node_parent->identifier : "-"), - (node->node_group ? "[GROUP]" : "")); + (node->node_group ? "[GROUP]" : + node->is_copy ? "[COPY]" : "")); + + if (!node->node_group && !node->parent_link) + num_leaf_nodes++; /* // use for dumping artwork info tree @@ -1413,10 +1819,15 @@ void dumpTreeInfo(TreeInfo *node, int depth) */ if (node->node_group != NULL) - dumpTreeInfo(node->node_group, depth + 1); + num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1); node = node->next; } + + if (depth == 0) + Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes); + + return num_leaf_nodes; } void sortTreeInfoBySortFunction(TreeInfo **node_first, @@ -1478,7 +1889,7 @@ void sortTreeInfo(TreeInfo **node_first) // some stuff from "files.c" // ============================================================================ -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) #ifndef S_IRGRP #define S_IRGRP S_IRUSR #endif @@ -1503,7 +1914,7 @@ void sortTreeInfo(TreeInfo **node_first) #ifndef S_ISGID #define S_ISGID 0 #endif -#endif // PLATFORM_WIN32 +#endif // PLATFORM_WINDOWS // file permissions for newly written files #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH) @@ -1527,7 +1938,7 @@ char *getHomeDir(void) { static char *dir = NULL; -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) if (dir == NULL) { dir = checked_malloc(MAX_PATH + 1); @@ -1535,6 +1946,8 @@ char *getHomeDir(void) if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir))) strcpy(dir, "."); } +#elif defined(PLATFORM_EMSCRIPTEN) + dir = PERSISTENT_DIRECTORY; #elif defined(PLATFORM_UNIX) if (dir == NULL) { @@ -1555,34 +1968,11 @@ char *getHomeDir(void) return dir; } -char *getCommonDataDir(void) -{ - static char *common_data_dir = NULL; - -#if defined(PLATFORM_WIN32) - if (common_data_dir == NULL) - { - char *dir = checked_malloc(MAX_PATH + 1); - - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir)) - && !strEqual(dir, "")) // empty for Windows 95/98 - common_data_dir = getPath2(dir, program.userdata_subdir); - else - common_data_dir = options.rw_base_directory; - } -#else - if (common_data_dir == NULL) - common_data_dir = options.rw_base_directory; -#endif - - return common_data_dir; -} - char *getPersonalDataDir(void) { static char *personal_data_dir = NULL; -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) if (personal_data_dir == NULL) personal_data_dir = getPath2(getHomeDir(), "Documents"); #else @@ -1636,7 +2026,7 @@ static mode_t posix_umask(mode_t mask) static int posix_mkdir(const char *pathname, mode_t mode) { -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) return mkdir(pathname); #else return mkdir(pathname, mode); @@ -1652,13 +2042,14 @@ static boolean posix_process_running_setgid(void) #endif } -void createDirectory(char *dir, char *text, int permission_class) +void createDirectory(char *dir, char *text) { if (directoryExists(dir)) return; // leave "other" permissions in umask untouched, but ensure group parts // of USERDATA_DIR_MODE are not masked + int permission_class = PERMS_PRIVATE; mode_t dir_mode = (permission_class == PERMS_PRIVATE ? DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC); mode_t last_umask = posix_umask(0); @@ -1687,17 +2078,17 @@ void createDirectory(char *dir, char *text, int permission_class) void InitMainUserDataDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); } void InitUserDataDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); if (user.nr != 0) { - createDirectory(getUserDir(-1), "users", PERMS_PRIVATE); - createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE); + createDirectory(getUserDir(-1), "users"); + createDirectory(getUserDir(user.nr), "user data"); } } @@ -1713,21 +2104,6 @@ void SetFilePermissions(char *filename, int permission_class) chmod(filename, perms); } -char *getCookie(char *file_type) -{ - static char cookie[MAX_COOKIE_LEN + 1]; - - if (strlen(program.cookie_prefix) + 1 + - strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN) - return "[COOKIE ERROR]"; // should never happen - - sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d", - program.cookie_prefix, file_type, - program.version_super, program.version_major); - - return cookie; -} - void fprintFileHeader(FILE *file, char *basename) { char *prefix = "# "; @@ -1935,7 +2311,7 @@ unsigned int get_hash_from_key(void *key) return hash; } -static int keys_are_equal(void *key1, void *key2) +int hash_keys_are_equal(void *key1, void *key2) { return (strEqual((char *)key1, (char *)key2)); } @@ -1943,7 +2319,7 @@ static int keys_are_equal(void *key1, void *key2) SetupFileHash *newSetupFileHash(void) { SetupFileHash *new_hash = - create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal); + create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal); if (new_hash == NULL) Fail("create_hashtable() failed -- out of memory"); @@ -2315,25 +2691,53 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename, return TRUE; } +static int compareSetupFileData(const void *object1, const void *object2) +{ + const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1; + const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2; + + return strcmp(entry1->token, entry2->token); +} + static void saveSetupFileHash(SetupFileHash *hash, char *filename) { + int item_count = hashtable_count(hash); + int item_size = sizeof(struct ConfigInfo); + struct ConfigInfo *sort_array = checked_malloc(item_count * item_size); FILE *file; + int i = 0; - if (!(file = fopen(filename, MODE_WRITE))) + // copy string pointers from hash to array + BEGIN_HASH_ITERATION(hash, itr) { - Warn("cannot write configuration file '%s'", filename); + sort_array[i].token = HASH_ITERATION_TOKEN(itr); + sort_array[i].value = HASH_ITERATION_VALUE(itr); - return; + i++; + + if (i > item_count) // should never happen + break; } + END_HASH_ITERATION(hash, itr) - BEGIN_HASH_ITERATION(hash, itr) + // sort string pointers from hash in array + qsort(sort_array, item_count, item_size, compareSetupFileData); + + if (!(file = fopen(filename, MODE_WRITE))) { - fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr), - HASH_ITERATION_VALUE(itr))); + Warn("cannot write configuration file '%s'", filename); + + return; } - END_HASH_ITERATION(hash, itr) + fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version", + program.version_string)); + for (i = 0; i < item_count; i++) + fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token, + sort_array[i].value)); fclose(file); + + checked_free(sort_array); } SetupFileList *loadSetupFileList(char *filename) @@ -2377,6 +2781,7 @@ SetupFileHash *loadSetupFileHash(char *filename) // ============================================================================ #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series" +#define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used" #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level" #define TOKEN_STR_HANDICAP_LEVEL "handicap_level" #define TOKEN_STR_LAST_USER "last_user" @@ -2409,11 +2814,15 @@ SetupFileHash *loadSetupFileHash(char *filename) #define LEVELINFO_TOKEN_FILENAME 24 #define LEVELINFO_TOKEN_FILETYPE 25 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26 -#define LEVELINFO_TOKEN_HANDICAP 27 -#define LEVELINFO_TOKEN_SKIP_LEVELS 28 -#define LEVELINFO_TOKEN_USE_EMC_TILES 29 +#define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27 +#define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28 +#define LEVELINFO_TOKEN_HANDICAP 29 +#define LEVELINFO_TOKEN_TIME_LIMIT 30 +#define LEVELINFO_TOKEN_SKIP_LEVELS 31 +#define LEVELINFO_TOKEN_USE_EMC_TILES 32 +#define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33 -#define NUM_LEVELINFO_TOKENS 30 +#define NUM_LEVELINFO_TOKENS 34 static LevelDirTree ldi; @@ -2447,9 +2856,13 @@ static struct TokenInfo levelinfo_tokens[] = { TYPE_STRING, &ldi.level_filename, "filename" }, { TYPE_STRING, &ldi.level_filetype, "filetype" }, { TYPE_STRING, &ldi.special_flags, "special_flags" }, + { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" }, + { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" }, { TYPE_BOOLEAN, &ldi.handicap, "handicap" }, + { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" }, { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }, - { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" } + { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }, + { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" } }; static struct TokenInfo artworkinfo_tokens[] = @@ -2467,7 +2880,6 @@ static struct TokenInfo artworkinfo_tokens[] = { TYPE_STRING, &ldi.basepath, "basepath" }, { TYPE_STRING, &ldi.fullpath, "fullpath" }, { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" }, - { TYPE_INTEGER, &ldi.color, "color" }, { TYPE_STRING, &ldi.class_desc, "class_desc" }, { -1, NULL, NULL }, @@ -2515,6 +2927,7 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority ti->latest_engine = FALSE; // default: get from level ti->parent_link = FALSE; + ti->is_copy = FALSE; ti->in_user_dir = FALSE; ti->user_defined = FALSE; ti->color = 0; @@ -2544,6 +2957,9 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->special_flags = NULL; + ti->empty_level_name = NULL; + ti->force_level_name = FALSE; + ti->levels = 0; ti->first_level = 0; ti->last_level = 0; @@ -2551,9 +2967,11 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->handicap_level = 0; ti->readonly = TRUE; ti->handicap = TRUE; + ti->time_limit = TRUE; ti->skip_levels = FALSE; ti->use_emc_tiles = FALSE; + ti->info_screens_from_main = FALSE; } } @@ -2596,6 +3014,7 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->sort_priority = parent->sort_priority; ti->latest_engine = parent->latest_engine; ti->parent_link = FALSE; + ti->is_copy = FALSE; ti->in_user_dir = parent->in_user_dir; ti->user_defined = parent->user_defined; ti->color = parent->color; @@ -2625,6 +3044,9 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->special_flags = getStringCopy(parent->special_flags); + ti->empty_level_name = getStringCopy(parent->empty_level_name); + ti->force_level_name = parent->force_level_name; + ti->levels = parent->levels; ti->first_level = parent->first_level; ti->last_level = parent->last_level; @@ -2632,9 +3054,11 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->handicap_level = parent->handicap_level; ti->readonly = parent->readonly; ti->handicap = parent->handicap; + ti->time_limit = parent->time_limit; ti->skip_levels = parent->skip_levels; ti->use_emc_tiles = parent->use_emc_tiles; + ti->info_screens_from_main = parent->info_screens_from_main; } } @@ -2687,6 +3111,9 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) ti_copy->special_flags = getStringCopy(ti->special_flags); + ti_copy->empty_level_name = getStringCopy(ti->empty_level_name); + ti_copy->force_level_name = ti->force_level_name; + ti_copy->levels = ti->levels; ti_copy->first_level = ti->first_level; ti_copy->last_level = ti->last_level; @@ -2696,13 +3123,16 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) ti_copy->level_group = ti->level_group; ti_copy->parent_link = ti->parent_link; + ti_copy->is_copy = ti->is_copy; ti_copy->in_user_dir = ti->in_user_dir; ti_copy->user_defined = ti->user_defined; ti_copy->readonly = ti->readonly; ti_copy->handicap = ti->handicap; + ti_copy->time_limit = ti->time_limit; ti_copy->skip_levels = ti->skip_levels; ti_copy->use_emc_tiles = ti->use_emc_tiles; + ti_copy->info_screens_from_main = ti->info_screens_from_main; ti_copy->color = ti->color; ti_copy->class_desc = getStringCopy(ti->class_desc); @@ -2822,40 +3252,13 @@ static int compareTreeInfoEntries(const void *object1, const void *object2) { const TreeInfo *entry1 = *((TreeInfo **)object1); const TreeInfo *entry2 = *((TreeInfo **)object2); - int class_sorting1 = 0, class_sorting2 = 0; - int compare_result; + int tree_sorting1 = TREE_SORTING(entry1); + int tree_sorting2 = TREE_SORTING(entry2); - if (entry1->type == TREE_TYPE_LEVEL_DIR) - { - class_sorting1 = LEVELSORTING(entry1); - class_sorting2 = LEVELSORTING(entry2); - } - else if (entry1->type == TREE_TYPE_GRAPHICS_DIR || - entry1->type == TREE_TYPE_SOUNDS_DIR || - entry1->type == TREE_TYPE_MUSIC_DIR) - { - class_sorting1 = ARTWORKSORTING(entry1); - class_sorting2 = ARTWORKSORTING(entry2); - } - - if (entry1->parent_link || entry2->parent_link) - compare_result = (entry1->parent_link ? -1 : +1); - else if (entry1->sort_priority == entry2->sort_priority) - { - char *name1 = getStringToLower(entry1->name_sorting); - char *name2 = getStringToLower(entry2->name_sorting); - - compare_result = strcmp(name1, name2); - - free(name1); - free(name2); - } - else if (class_sorting1 == class_sorting2) - compare_result = entry1->sort_priority - entry2->sort_priority; + if (tree_sorting1 != tree_sorting2) + return (tree_sorting1 - tree_sorting2); else - compare_result = class_sorting1 - class_sorting2; - - return compare_result; + return strcasecmp(entry1->name_sorting, entry2->name_sorting); } static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent) @@ -2872,13 +3275,13 @@ static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent) ti_new->parent_link = TRUE; setString(&ti_new->identifier, node_parent->identifier); - setString(&ti_new->name, ".. (parent directory)"); + setString(&ti_new->name, BACKLINK_TEXT_PARENT); setString(&ti_new->name_sorting, ti_new->name); setString(&ti_new->subdir, STRING_PARENT_DIRECTORY); setString(&ti_new->fullpath, node_parent->fullpath); - ti_new->sort_priority = node_parent->sort_priority; + ti_new->sort_priority = LEVELCLASS_PARENT; ti_new->latest_engine = node_parent->latest_engine; setString(&ti_new->class_desc, getLevelClassDescription(ti_new)); @@ -2890,40 +3293,64 @@ static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent) static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first) { - TreeInfo *ti_new, *ti_new2; - if (node_first == NULL) return NULL; - ti_new = newTreeInfo(); - setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR); + TreeInfo *ti_new = newTreeInfo(); + int type = node_first->type; + + setTreeInfoToDefaults(ti_new, type); ti_new->node_parent = NULL; ti_new->parent_link = FALSE; - setString(&ti_new->identifier, node_first->identifier); - setString(&ti_new->name, "level sets"); + setString(&ti_new->identifier, "top_tree_node"); + setString(&ti_new->name, TREE_INFOTEXT(type)); setString(&ti_new->name_sorting, ti_new->name); setString(&ti_new->subdir, STRING_TOP_DIRECTORY); setString(&ti_new->fullpath, "."); - ti_new->sort_priority = node_first->sort_priority;; + ti_new->sort_priority = LEVELCLASS_TOP; ti_new->latest_engine = node_first->latest_engine; - setString(&ti_new->class_desc, "level sets"); + setString(&ti_new->class_desc, TREE_INFOTEXT(type)); ti_new->node_group = node_first; ti_new->level_group = TRUE; - ti_new2 = createParentTreeInfoNode(ti_new); + TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new); - setString(&ti_new2->name, ".. (main menu)"); + setString(&ti_new2->name, TREE_BACKLINK_TEXT(type)); setString(&ti_new2->name_sorting, ti_new2->name); return ti_new; } +static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent) +{ + while (node) + { + if (node->node_group) + setTreeInfoParentNodes(node->node_group, node); + + node->node_parent = node_parent; + + node = node->next; + } +} + +TreeInfo *addTopTreeInfoNode(TreeInfo *node_first) +{ + // add top tree node with back link node in previous tree + node_first = createTopTreeInfoNode(node_first); + + // set all parent links (back links) in complete tree + setTreeInfoParentNodes(node_first, NULL); + + return node_first; +} + // ---------------------------------------------------------------------------- // functions for handling level and custom artwork info cache @@ -2940,6 +3367,17 @@ static void LoadArtworkInfoCache(void) // try to load artwork info hash from already existing cache file artworkinfo_cache_old = loadSetupFileHash(filename); + // try to get program version that artwork info cache was written with + char *version = getHashEntry(artworkinfo_cache_old, "program.version"); + + // check program version of artwork info cache against current version + if (!strEqual(version, program.version_string)) + { + freeSetupFileHash(artworkinfo_cache_old); + + artworkinfo_cache_old = NULL; + } + // if no artwork info cache file was found, start with empty hash if (artworkinfo_cache_old == NULL) artworkinfo_cache_old = newSetupFileHash(); @@ -2949,10 +3387,15 @@ static void LoadArtworkInfoCache(void) if (artworkinfo_cache_new == NULL) artworkinfo_cache_new = newSetupFileHash(); + + update_artworkinfo_cache = FALSE; } static void SaveArtworkInfoCache(void) { + if (!update_artworkinfo_cache) + return; + char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE); InitCacheDirectory(); @@ -2997,6 +3440,9 @@ static boolean modifiedFileTimestamp(char *filename, char *timestamp_string) if (timestamp_string == NULL) return TRUE; + if (!fileExists(filename)) // file does not exist + return (atoi(timestamp_string) != 0); + if (stat(filename, &file_status) != 0) // cannot stat file return TRUE; @@ -3451,15 +3897,13 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first, leveldir_new->user_defined = (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new)); - leveldir_new->color = LEVELCOLOR(leveldir_new); - setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new)); leveldir_new->handicap_level = // set handicap to default value (leveldir_new->user_defined || !leveldir_new->handicap ? leveldir_new->last_level : leveldir_new->first_level); - DrawInitText(leveldir_new->name, 150, FC_YELLOW); + DrawInitTextItem(leveldir_new->name); pushTreeInfo(node_first, leveldir_new); @@ -3546,9 +3990,13 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, level_directory, "."); } - if (!valid_entry_found) + boolean valid_entry_expected = + (strEqual(level_directory, options.level_directory) || + setup.internal.create_user_levelset); + + if (valid_entry_expected && !valid_entry_found) Warn("cannot find any valid level series in directory '%s'", - level_directory); + level_directory); } boolean AdjustGraphicsForEMC(void) @@ -3575,7 +4023,7 @@ void LoadLevelInfo(void) { InitUserLevelDirectory(getLoginName()); - DrawInitText("Loading level series", 120, FC_GREEN); + DrawInitTextHead("Loading level series"); LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory); LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL)); @@ -3696,9 +4144,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first, artwork_new->in_user_dir = (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type))); - // (may use ".sort_priority" from "setup_file_hash" above) - artwork_new->color = ARTWORKCOLOR(artwork_new); - setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new)); if (setup_file_hash == NULL) // (after determining ".user_defined") @@ -3716,9 +4161,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first, artwork_new->sort_priority = ARTWORKCLASS_CLASSICS; } - // set to new values after changing ".sort_priority" - artwork_new->color = ARTWORKCOLOR(artwork_new); - setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new)); } @@ -3854,7 +4296,7 @@ void LoadArtworkInfo(void) { LoadArtworkInfoCache(); - DrawInitText("Looking for custom artwork", 120, FC_GREEN); + DrawInitTextHead("Looking for custom artwork"); LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL, options.graphics_directory, @@ -3913,8 +4355,33 @@ void LoadArtworkInfo(void) #endif } -static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node, - LevelDirTree *level_node) +static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node) +{ + ArtworkDirTree *artwork_new = newTreeInfo(); + char *top_node_name = "standalone artwork"; + + setTreeInfoToDefaults(artwork_new, (*artwork_node)->type); + + artwork_new->level_group = TRUE; + + setString(&artwork_new->identifier, top_node_name); + setString(&artwork_new->name, top_node_name); + setString(&artwork_new->name_sorting, top_node_name); + + // create node to link back to current custom artwork directory + createParentTreeInfoNode(artwork_new); + + // move existing custom artwork tree into newly created sub-tree + artwork_new->node_group->next = *artwork_node; + + // change custom artwork tree to contain only newly created node + *artwork_node = artwork_new; +} + +static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node, + ArtworkDirTree *node_parent, + LevelDirTree *level_node, + boolean empty_level_set_mode) { int type = (*artwork_node)->type; @@ -3922,8 +4389,10 @@ static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node, while (level_node) { + boolean empty_level_set = (level_node->levels == 0); + // check all tree entries for artwork, but skip parent link entries - if (!level_node->parent_link) + if (!level_node->parent_link && empty_level_set == empty_level_set_mode) { TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type); boolean cached = (artwork_new != NULL); @@ -3949,7 +4418,9 @@ static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node, setString(&artwork_new->name_sorting, level_node->name_sorting); artwork_new->sort_priority = level_node->sort_priority; - artwork_new->color = LEVELCOLOR(artwork_new); + artwork_new->in_user_dir = level_node->in_user_dir; + + update_artworkinfo_cache = TRUE; } free(path); @@ -3960,28 +4431,81 @@ static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node, setArtworkInfoCacheEntry(artwork_new, level_node, type); } - DrawInitText(level_node->name, 150, FC_YELLOW); + DrawInitTextItem(level_node->name); if (level_node->node_group != NULL) - LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group); + { + TreeInfo *artwork_new = newTreeInfo(); + + if (node_parent) + setTreeInfoToDefaultsFromParent(artwork_new, node_parent); + else + setTreeInfoToDefaults(artwork_new, type); + + artwork_new->level_group = TRUE; + + setString(&artwork_new->identifier, level_node->subdir); + + if (node_parent == NULL) // check for top tree node + { + char *top_node_name = (empty_level_set_mode ? + "artwork for certain level sets" : + "artwork included in level sets"); + + setString(&artwork_new->name, top_node_name); + setString(&artwork_new->name_sorting, top_node_name); + } + else + { + setString(&artwork_new->name, level_node->name); + setString(&artwork_new->name_sorting, level_node->name_sorting); + } + + pushTreeInfo(artwork_node, artwork_new); + + // create node to link back to current custom artwork directory + createParentTreeInfoNode(artwork_new); + + // recursively step into sub-directory and look for more custom artwork + LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new, + level_node->node_group, + empty_level_set_mode); + + // if sub-tree has no custom artwork at all, remove it + if (artwork_new->node_group->next == NULL) + removeTreeInfo(artwork_node); + } level_node = level_node->next; } } +static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node) +{ + // move peviously loaded artwork tree into separate sub-tree + MoveArtworkInfoIntoSubTree(artwork_node); + + // load artwork from level sets into separate sub-trees + LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE); + LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE); + + // add top tree node over all sub-trees and set parent links + *artwork_node = addTopTreeInfoNode(*artwork_node); +} + void LoadLevelArtworkInfo(void) { print_timestamp_init("LoadLevelArtworkInfo"); - DrawInitText("Looking for custom level artwork", 120, FC_GREEN); + DrawInitTextHead("Looking for custom level artwork"); print_timestamp_time("DrawTimeText"); - LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all); + LoadArtworkInfoFromLevelInfo(&artwork.gfx_first); print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)"); - LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all); + LoadArtworkInfoFromLevelInfo(&artwork.snd_first); print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)"); - LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all); + LoadArtworkInfoFromLevelInfo(&artwork.mus_first); print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)"); SaveArtworkInfoCache(); @@ -4070,6 +4594,12 @@ static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir, TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first, tree_subdir_new); + // if not found, check if added node is level group or artwork group + if (tree_node_new == NULL) + tree_node_new = getTreeInfoFromIdentifierExt(*tree_node_first, + tree_subdir_new, + TREE_NODE_TYPE_GROUP); + if (tree_node_new == NULL) // should not happen return FALSE; @@ -4223,7 +4753,7 @@ boolean CreateUserLevelSet(char *level_subdir, char *level_name, int i; // create user level sub-directory, if needed - createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE); + createDirectory(getUserLevelDir(level_subdir), "user level"); filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME); @@ -4421,9 +4951,11 @@ static void InitLastPlayedLevels_ParentNode(void) setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first); leveldir_new->level_group = TRUE; + leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL; setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES); setString(&leveldir_new->name, "<< (last played level sets)"); + setString(&leveldir_new->name_sorting, leveldir_new->name); pushTreeInfo(leveldir_top, leveldir_new); @@ -4434,7 +4966,6 @@ static void InitLastPlayedLevels_ParentNode(void) void UpdateLastPlayedLevels_TreeInfo(void) { char **last_level_series = setup.level_setup.last_level_series; - boolean reset_leveldir_current = FALSE; LevelDirTree *leveldir_last; TreeInfo **node_new = NULL; int i; @@ -4444,13 +4975,9 @@ void UpdateLastPlayedLevels_TreeInfo(void) InitLastPlayedLevels_ParentNode(); - // check if current level set is from "last played" sub-tree to be rebuilt - reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier, - TOKEN_STR_LAST_LEVEL_SERIES); - leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first, TOKEN_STR_LAST_LEVEL_SERIES, - TRUE); + TREE_NODE_TYPE_GROUP); if (leveldir_last == NULL) return; @@ -4458,16 +4985,22 @@ void UpdateLastPlayedLevels_TreeInfo(void) freeTreeInfo(*node_new); + *node_new = NULL; + for (i = 0; last_level_series[i] != NULL; i++) { LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first, last_level_series[i]); + if (node_last == NULL) + continue; *node_new = getTreeInfoCopy(node_last); // copy complete node (*node_new)->node_top = &leveldir_first; // correct top node link (*node_new)->node_parent = leveldir_last; // correct parent node link + (*node_new)->is_copy = TRUE; // mark entry as node copy + (*node_new)->node_group = NULL; (*node_new)->next = NULL; @@ -4475,10 +5008,6 @@ void UpdateLastPlayedLevels_TreeInfo(void) node_new = &((*node_new)->next); } - - if (reset_leveldir_current) - leveldir_current = getTreeInfoFromIdentifier(leveldir_first, - last_level_series[0]); } static void UpdateLastPlayedLevels_List(void) @@ -4500,6 +5029,53 @@ static void UpdateLastPlayedLevels_List(void) setString(&last_level_series[0], leveldir_current->identifier); } +#define LAST_PLAYED_MODE_SET 1 +#define LAST_PLAYED_MODE_SET_FORCED 2 +#define LAST_PLAYED_MODE_GET 3 + +static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode) +{ + static char *identifier = NULL; + + if (mode == LAST_PLAYED_MODE_SET) + { + setString(&identifier, (node && node->is_copy ? node->identifier : NULL)); + } + else if (mode == LAST_PLAYED_MODE_SET_FORCED) + { + setString(&identifier, (node ? node->identifier : NULL)); + } + else if (mode == LAST_PLAYED_MODE_GET) + { + TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first, + identifier, + TREE_NODE_TYPE_COPY); + return (node_new != NULL ? node_new : node); + } + + return NULL; // not used +} + +void StoreLastPlayedLevels(TreeInfo *node) +{ + StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET); +} + +void ForcedStoreLastPlayedLevels(TreeInfo *node) +{ + StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED); +} + +void RestoreLastPlayedLevels(TreeInfo **node) +{ + *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET); +} + +boolean CheckLastPlayedLevels(void) +{ + return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL); +} + void LoadLevelSetup_LastSeries(void) { // -------------------------------------------------------------------------- @@ -4535,6 +5111,13 @@ void LoadLevelSetup_LastSeries(void) if (leveldir_current == NULL) leveldir_current = getFirstValidTreeInfoEntry(leveldir_first); + char *last_played_menu_used = + getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED); + + // store if last level set was selected from "last played" menu + if (strEqual(last_played_menu_used, "true")) + ForcedStoreLastPlayedLevels(leveldir_current); + for (i = 0; i < MAX_LEVELDIR_HISTORY; i++) { char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10]; @@ -4597,11 +5180,18 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series) fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated"); fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES, - leveldir_current->identifier)); + leveldir_current->identifier)); + + // store if last level set was selected from "last played" menu + boolean last_played_menu_used = CheckLastPlayedLevels(); + char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used); + + fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED, + setup_value)); for (i = 0; last_level_series[i] != NULL; i++) { - char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10]; + char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1]; sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);