#include "platform.h"
-#if !defined(PLATFORM_WIN32)
-#include <pwd.h>
-#include <sys/param.h>
-#endif
-
#include "setup.h"
#include "joystick.h"
#include "text.h"
"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);
static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
static SetupFileHash *artworkinfo_cache_new = NULL;
static SetupFileHash *optional_tokens_hash = NULL;
static boolean use_artworkinfo_cache = TRUE;
+static boolean update_artworkinfo_cache = FALSE;
// ----------------------------------------------------------------------------
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;
if (score_dir == NULL)
{
if (program.global_scores)
- score_dir = getPath2(getCommonDataDir(), score_subdir);
+ score_dir = getPath2(getCommonDataDir(), score_subdir);
else
- score_dir = getPath2(getUserGameDataDir(), score_subdir);
+ score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
}
if (level_subdir != NULL)
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);
+
+ score_level_dir = getPath2(score_dir, level_subdir);
+
+ return score_level_dir;
+ }
+
+ 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 *getUserSubdir(int nr)
+{
+ static char user_subdir[16] = { 0 };
+
+ sprintf(user_subdir, "%03d", nr);
+
+ return user_subdir;
+}
+
+static char *getUserDir(int nr)
+{
+ static char *user_dir = NULL;
+ char *main_data_dir = getMainUserGameDataDir();
+ char *users_subdir = USERS_DIRECTORY;
+ char *user_subdir = getUserSubdir(nr);
+
+ checked_free(user_dir);
+
+ if (nr != -1)
+ user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
+ else
+ user_dir = getPath2(main_data_dir, users_subdir);
+
+ return user_dir;
+}
+
static char *getLevelSetupDir(char *level_subdir)
{
static char *levelsetup_dir = NULL;
return levelsetup_dir;
}
-static char *getCacheDir(void)
-{
- static char *cache_dir = NULL;
-
- if (cache_dir == NULL)
- cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
-
- return cache_dir;
-}
-
static char *getNetworkDir(void)
{
static char *network_dir = NULL;
if (network_dir == NULL)
- network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
+ network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
return network_dir;
}
char *getUserLevelDir(char *level_subdir)
{
static char *userlevel_dir = NULL;
- char *data_dir = getUserGameDataDir();
+ char *data_dir = getMainUserGameDataDir();
char *userlevel_subdir = LEVELS_DIRECTORY;
checked_free(userlevel_dir);
static char *usergraphics_dir = NULL;
if (usergraphics_dir == NULL)
- usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
+ usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
return usergraphics_dir;
}
static char *usersounds_dir = NULL;
if (usersounds_dir == NULL)
- usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
+ usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
return usersounds_dir;
}
static char *usermusic_dir = NULL;
if (usermusic_dir == NULL)
- usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
+ usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
return usermusic_dir;
}
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 *getSetupFilename(void)
{
static char *filename = NULL;
if (program.global_scores)
createDirectory(getCommonDataDir(), "common data", permissions);
else
- createDirectory(getUserGameDataDir(), "user data", permissions);
+ createDirectory(getMainUserGameDataDir(), "main user data", permissions);
createDirectory(getScoreDir(NULL), "main score", permissions);
createDirectory(getScoreDir(level_subdir), "level score", permissions);
}
+void InitScoreCacheDirectory(char *level_subdir)
+{
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+ createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
+ createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
+ createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
+}
+
+void InitScoreTapeDirectory(char *level_subdir, int nr)
+{
+ int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
+
+ InitScoreDirectory(level_subdir);
+
+ createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", permissions);
+}
+
static void SaveUserLevelInfo(void);
void InitUserLevelDirectory(char *level_subdir)
{
if (!directoryExists(getUserLevelDir(level_subdir)))
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
{
if (!directoryExists(getNetworkLevelDir(level_subdir)))
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ 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);
static void InitCacheDirectory(void)
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
}
*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;
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)
return numTreeInfo(getTreeInfoFirstGroupEntry(node));
}
-int posTreeInfo(TreeInfo *node)
+int getPosFromTreeInfo(TreeInfo *node)
{
TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
int pos = 0;
return node_default;
}
-TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
+static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
+ 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)
{
- TreeInfo *node_group;
-
- node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
-
+ TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
+ identifier,
+ node_type_wanted);
if (node_group)
return node_group;
}
- else if (!node->parent_link)
- {
- if (strEqual(identifier, node->identifier))
- return node;
- }
node = node->next;
}
return NULL;
}
+TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
+{
+ return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
+}
+
static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
TreeInfo *node, boolean skip_sets_without_levels)
{
void dumpTreeInfo(TreeInfo *node, int depth)
{
+ char bullet_list[] = { '-', '*', 'o' };
int i;
- printf("Dumping TreeInfo:\n");
+ if (depth == 0)
+ Debug("tree", "Dumping TreeInfo:");
while (node)
{
- for (i = 0; i < (depth + 1) * 3; i++)
- printf(" ");
+ char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
+
+ for (i = 0; i < depth * 2; i++)
+ DebugContinued("", " ");
- printf("'%s' / '%s'\n", node->identifier, node->name);
+ 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]" : ""));
/*
// use for dumping artwork info tree
- printf("subdir == '%s' ['%s', '%s'] [%d])\n",
- node->subdir, node->fullpath, node->basepath, node->in_user_dir);
+ Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
+ node->subdir, node->fullpath, node->basepath, node->in_user_dir);
*/
if (node->node_group != NULL)
if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
strcpy(dir, ".");
}
+#elif defined(PLATFORM_EMSCRIPTEN)
+ dir = "/persistent";
#elif defined(PLATFORM_UNIX)
if (dir == NULL)
{
if ((dir = getenv("HOME")) == NULL)
{
- struct passwd *pwd;
+ dir = getUnixHomeDir();
- if ((pwd = getpwuid(getuid())) != NULL)
- dir = getStringCopy(pwd->pw_dir);
+ if (dir != NULL)
+ dir = getStringCopy(dir);
else
dir = ".";
}
return personal_data_dir;
}
-char *getUserGameDataDir(void)
+char *getMainUserGameDataDir(void)
{
- static char *user_game_data_dir = NULL;
+ static char *main_user_data_dir = NULL;
#if defined(PLATFORM_ANDROID)
- if (user_game_data_dir == NULL)
- user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
+ if (main_user_data_dir == NULL)
+ main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
SDL_AndroidGetExternalStoragePath() :
SDL_AndroidGetInternalStoragePath());
#else
- if (user_game_data_dir == NULL)
- user_game_data_dir = getPath2(getPersonalDataDir(),
+ if (main_user_data_dir == NULL)
+ main_user_data_dir = getPath2(getPersonalDataDir(),
program.userdata_subdir);
#endif
- return user_game_data_dir;
+ return main_user_data_dir;
+}
+
+char *getUserGameDataDir(void)
+{
+ if (user.nr == 0)
+ return getMainUserGameDataDir();
+ else
+ return getUserDir(user.nr);
}
char *getSetupDir(void)
posix_umask(last_umask); // restore previous umask
}
+void InitMainUserDataDirectory(void)
+{
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+}
+
void InitUserDataDirectory(void)
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+
+ if (user.nr != 0)
+ {
+ createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
+ createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
+ }
}
void SetFilePermissions(char *filename, int permission_class)
create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
if (new_hash == NULL)
- Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
+ Fail("create_hashtable() failed -- out of memory");
return new_hash;
}
// change value; if it does not exist, insert it as new
if (!change_hash_entry(hash, token, value_copy))
if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
- Error(ERR_EXIT, "cannot insert into hash -- aborting");
+ Fail("cannot insert into hash -- aborting");
}
char *removeHashEntry(SetupFileHash *hash, char *token)
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)
#define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
#define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
#define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
+#define TOKEN_STR_LAST_USER "last_user"
// level directory info
#define LEVELINFO_TOKEN_IDENTIFIER 0
{ 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 },
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;
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;
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;
{
const TreeInfo *entry1 = *((TreeInfo **)object1);
const TreeInfo *entry2 = *((TreeInfo **)object2);
- int class_sorting1 = 0, class_sorting2 = 0;
- int compare_result;
-
- 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);
+ int tree_sorting1 = TREE_SORTING(entry1);
+ int tree_sorting2 = TREE_SORTING(entry2);
- 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)
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));
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;
+ }
+}
+
// ----------------------------------------------------------------------------
// functions for handling level and custom artwork info cache
// 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();
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();
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;
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_current = getFirstValidTreeInfoEntry(leveldir_first);
if (leveldir_first == NULL)
- Error(ERR_EXIT, "cannot find any valid level series in any directory");
+ Fail("cannot find any valid level series in any directory");
sortTreeInfo(&leveldir_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")
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));
}
return artwork_new;
}
+void SetCurrentArtwork(int type)
+{
+ ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
+ ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
+ char *setup_set = SETUP_ARTWORK_SET(setup, type);
+ char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
+
+ // set current artwork to artwork configured in setup menu
+ *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
+
+ // if not found, set current artwork to default artwork
+ if (*current_ptr == NULL)
+ *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
+
+ // if not found, set current artwork to first artwork in tree
+ if (*current_ptr == NULL)
+ *current_ptr = getFirstValidTreeInfoEntry(first_node);
+}
+
+void ChangeCurrentArtworkIfNeeded(int type)
+{
+ char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
+ char *setup_set = SETUP_ARTWORK_SET(setup, type);
+
+ if (!strEqual(current_identifier, setup_set))
+ SetCurrentArtwork(type);
+}
+
void LoadArtworkInfo(void)
{
LoadArtworkInfoCache();
artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
// before sorting, the first entries will be from the user directory
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
-
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
- if (artwork.snd_current == NULL)
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
- if (artwork.snd_current == NULL)
- artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
-
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
- if (artwork.mus_current == NULL)
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
- if (artwork.mus_current == NULL)
- artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
+ SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
+ SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
+ SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
artwork.gfx_current_identifier = artwork.gfx_current->identifier;
artwork.snd_current_identifier = artwork.snd_current->identifier;
#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;
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);
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);
DrawInitText(level_node->name, 150, FC_YELLOW);
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 three separate sub-trees
+ *artwork_node = createTopTreeInfoNode(*artwork_node);
+
+ // set all parent links (back links) in complete artwork tree
+ setTreeInfoParentNodes(*artwork_node, NULL);
+}
+
void LoadLevelArtworkInfo(void)
{
print_timestamp_init("LoadLevelArtworkInfo");
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();
print_timestamp_time("SaveArtworkInfoCache");
// needed for reloading level artwork not known at ealier stage
-
- if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
- {
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
- }
-
- if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
- {
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
- if (artwork.snd_current == NULL)
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
- if (artwork.snd_current == NULL)
- artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
- }
-
- if (!strEqual(artwork.mus_current_identifier, setup.music_set))
- {
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
- if (artwork.mus_current == NULL)
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
- if (artwork.mus_current == NULL)
- artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
- }
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
print_timestamp_time("getTreeInfoFromIdentifier");
char *tree_subdir_new, int type)
{
if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
- Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
+ Fail("internal tree info structure corrupted -- aborting");
}
void AddUserLevelSetToLevelInfo(char *level_subdir_new)
ti = getTreeInfoFromIdentifier(artwork_first_node,
ARTWORK_DEFAULT_SUBDIR(type));
if (ti == NULL)
- Error(ERR_EXIT, "cannot find default graphics -- should not happen");
+ Fail("cannot find default graphics -- should not happen");
}
return ti;
return line;
}
+static void InitLastPlayedLevels_ParentNode(void)
+{
+ LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
+ LevelDirTree *leveldir_new = NULL;
+
+ // check if parent node for last played levels already exists
+ if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
+ return;
+
+ leveldir_new = newTreeInfo();
+
+ 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);
+
+ // create node to link back to current level directory
+ createParentTreeInfoNode(leveldir_new);
+}
+
+void UpdateLastPlayedLevels_TreeInfo(void)
+{
+ char **last_level_series = setup.level_setup.last_level_series;
+ LevelDirTree *leveldir_last;
+ TreeInfo **node_new = NULL;
+ int i;
+
+ if (last_level_series[0] == NULL)
+ return;
+
+ InitLastPlayedLevels_ParentNode();
+
+ leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
+ TOKEN_STR_LAST_LEVEL_SERIES,
+ TREE_NODE_TYPE_GROUP);
+ if (leveldir_last == NULL)
+ return;
+
+ node_new = &leveldir_last->node_group->next;
+
+ 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;
+
+ (*node_new)->cl_first = -1; // force setting tree cursor
+
+ node_new = &((*node_new)->next);
+ }
+}
+
+static void UpdateLastPlayedLevels_List(void)
+{
+ char **last_level_series = setup.level_setup.last_level_series;
+ int pos = MAX_LEVELDIR_HISTORY - 1;
+ int i;
+
+ // search for potentially already existing entry in list of level sets
+ for (i = 0; last_level_series[i] != NULL; i++)
+ if (strEqual(last_level_series[i], leveldir_current->identifier))
+ pos = i;
+
+ // move list of level sets one entry down (using potentially free entry)
+ for (i = pos; i > 0; i--)
+ setString(&last_level_series[i], last_level_series[i - 1]);
+
+ // put last played level set at top position
+ setString(&last_level_series[0], leveldir_current->identifier);
+}
+
+static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
+{
+ static char *identifier = NULL;
+
+ if (store)
+ {
+ setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
+
+ return NULL; // not used
+ }
+ else
+ {
+ TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
+ identifier,
+ TREE_NODE_TYPE_COPY);
+ return (node_new != NULL ? node_new : node);
+ }
+}
+
+void StoreLastPlayedLevels(TreeInfo *node)
+{
+ StoreOrRestoreLastPlayedLevels(node, TRUE);
+}
+
+void RestoreLastPlayedLevels(TreeInfo **node)
+{
+ *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
+}
+
void LoadLevelSetup_LastSeries(void)
{
// --------------------------------------------------------------------------
char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
SetupFileHash *level_setup_hash = NULL;
+ int pos = 0;
+ int i;
// always start with reliable default values
leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+ // start with empty history of last played level sets
+ setString(&setup.level_setup.last_level_series[0], NULL);
+
if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
{
leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
if (leveldir_current == NULL)
leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+ for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
+ {
+ char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
+ LevelDirTree *leveldir_last;
+
+ sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
+
+ last_level_series = getHashEntry(level_setup_hash, token);
+
+ leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
+ last_level_series);
+ if (leveldir_last != NULL)
+ setString(&setup.level_setup.last_level_series[pos++],
+ last_level_series);
+ }
+
+ setString(&setup.level_setup.last_level_series[pos], NULL);
+
freeSetupFileHash(level_setup_hash);
}
else
if (leveldir_current == NULL)
return;
+ char **last_level_series = setup.level_setup.last_level_series;
char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
- char *level_subdir = leveldir_current->subdir;
FILE *file;
+ int i;
InitUserDataDirectory();
+ UpdateLastPlayedLevels_List();
+
if (!(file = fopen(filename, MODE_WRITE)))
{
Warn("cannot write setup file '%s'", filename);
if (deactivate_last_level_series)
fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
- fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
- level_subdir));
+ fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
+ leveldir_current->identifier));
+
+ for (i = 0; last_level_series[i] != NULL; i++)
+ {
+ char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
+
+ sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
+
+ fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
+ }
fclose(file);
if (nr >= 0 && nr < MAX_LEVELS)
level_stats[nr].solved++;
}
+
+void LoadUserSetup(void)
+{
+ // --------------------------------------------------------------------------
+ // ~/.<program>/usersetup.conf
+ // --------------------------------------------------------------------------
+
+ char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
+ SetupFileHash *user_setup_hash = NULL;
+
+ // always start with reliable default values
+ user.nr = 0;
+
+ if ((user_setup_hash = loadSetupFileHash(filename)))
+ {
+ char *token_value;
+
+ // get last selected user number
+ token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
+
+ if (token_value)
+ user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
+
+ freeSetupFileHash(user_setup_hash);
+ }
+ else
+ {
+ Debug("setup", "using default setup values");
+ }
+
+ free(filename);
+}
+
+void SaveUserSetup(void)
+{
+ // --------------------------------------------------------------------------
+ // ~/.<program>/usersetup.conf
+ // --------------------------------------------------------------------------
+
+ char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
+ FILE *file;
+
+ InitMainUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot write setup file '%s'", filename);
+
+ free(filename);
+
+ return;
+ }
+
+ fprintFileHeader(file, USERSETUP_FILENAME);
+
+ fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
+ i_to_a(user.nr)));
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+
+ free(filename);
+}