added "last played level sets" sub-menu to level set selection screen
[rocksndiamonds.git] / src / libgame / setup.c
index 2a86f2be07706c0380ea7ad7a13494f401a1ca39..2bc46d413a2ebbcbacbfdb9132e4b0212efaa327 100644 (file)
@@ -121,9 +121,9 @@ static char *getScoreDir(char *level_subdir)
   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)
@@ -138,6 +138,32 @@ static char *getScoreDir(char *level_subdir)
   return score_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;
@@ -159,7 +185,7 @@ static char *getCacheDir(void)
   static char *cache_dir = NULL;
 
   if (cache_dir == NULL)
-    cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
+    cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
 
   return cache_dir;
 }
@@ -169,7 +195,7 @@ 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;
 }
@@ -192,7 +218,7 @@ char *getLevelDirFromTreeInfo(TreeInfo *node)
 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);
@@ -341,7 +367,7 @@ char *getUserGraphicsDir(void)
   static char *usergraphics_dir = NULL;
 
   if (usergraphics_dir == NULL)
-    usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
+    usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
 
   return usergraphics_dir;
 }
@@ -351,7 +377,7 @@ char *getUserSoundsDir(void)
   static char *usersounds_dir = NULL;
 
   if (usersounds_dir == NULL)
-    usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
+    usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
 
   return usersounds_dir;
 }
@@ -361,7 +387,7 @@ char *getUserMusicDir(void)
   static char *usermusic_dir = NULL;
 
   if (usermusic_dir == NULL)
-    usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
+    usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
 
   return usermusic_dir;
 }
@@ -1067,7 +1093,7 @@ void InitScoreDirectory(char *level_subdir)
   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);
@@ -1079,7 +1105,7 @@ 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);
 
@@ -1092,7 +1118,7 @@ void InitNetworkLevelDirectory(char *level_subdir)
 {
   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);
@@ -1108,7 +1134,7 @@ void InitLevelSetupDirectory(char *level_subdir)
 
 static void InitCacheDirectory(void)
 {
-  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
 }
 
@@ -1189,7 +1215,7 @@ int numTreeInfoInGroup(TreeInfo *node)
   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
 }
 
-int posTreeInfo(TreeInfo *node)
+int getPosFromTreeInfo(TreeInfo *node)
 {
   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
   int pos = 0;
@@ -1223,7 +1249,8 @@ TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
   return node_default;
 }
 
-TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
+static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
+                                             boolean include_node_groups)
 {
   if (identifier == NULL)
     return NULL;
@@ -1232,10 +1259,12 @@ TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
   {
     if (node->node_group)
     {
-      TreeInfo *node_group;
-
-      node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
+      if (include_node_groups && strEqual(identifier, node->identifier))
+       return node;
 
+      TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
+                                                         identifier,
+                                                         include_node_groups);
       if (node_group)
        return node_group;
     }
@@ -1251,6 +1280,11 @@ TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
   return NULL;
 }
 
+TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
+{
+  return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
+}
+
 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
                               TreeInfo *node, boolean skip_sets_without_levels)
 {
@@ -1552,23 +1586,31 @@ char *getPersonalDataDir(void)
   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)
@@ -1636,9 +1678,20 @@ void createDirectory(char *dir, char *text, int permission_class)
   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)
@@ -2319,6 +2372,7 @@ SetupFileHash *loadSetupFileHash(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
@@ -4346,6 +4400,99 @@ char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
   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;
+
+  setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
+  setString(&leveldir_new->name, "<< (last played level sets)");
+
+  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;
+  boolean reset_leveldir_current = FALSE;
+  LevelDirTree *leveldir_last;
+  TreeInfo **node_new = NULL;
+  int i;
+
+  if (last_level_series[0] == NULL)
+    return;
+
+  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);
+  if (leveldir_last == NULL)
+    return;
+
+  node_new = &leveldir_last->node_group->next;
+
+  freeTreeInfo(*node_new);
+
+  for (i = 0; last_level_series[i] != NULL; i++)
+  {
+    LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
+                                                       last_level_series[i]);
+
+    *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)->node_group = NULL;
+    (*node_new)->next = NULL;
+
+    (*node_new)->cl_first = -1;                        // force setting tree cursor
+
+    node_new = &((*node_new)->next);
+  }
+
+  if (reset_leveldir_current)
+    leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
+                                                 last_level_series[0]);
+}
+
+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);
+}
+
 void LoadLevelSetup_LastSeries(void)
 {
   // --------------------------------------------------------------------------
@@ -4354,10 +4501,15 @@ 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
+  setup.level_setup.last_level_series[0] = NULL;
+
   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
   {
     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
@@ -4376,6 +4528,24 @@ void LoadLevelSetup_LastSeries(void)
     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)
+       setup.level_setup.last_level_series[pos++] =
+         getStringCopy(last_level_series);
+    }
+
+    setup.level_setup.last_level_series[pos] = NULL;
+
     freeSetupFileHash(level_setup_hash);
   }
   else
@@ -4396,12 +4566,15 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
   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);
@@ -4416,8 +4589,17 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
   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);
 
@@ -4681,3 +4863,66 @@ void LevelStats_incSolved(int nr)
   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);
+}