SongBrowserModel.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using SongBrowserPlugin.DataAccess;
  2. using SongLoaderPlugin;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using UnityEngine;
  9. namespace SongBrowserPlugin
  10. {
  11. public class SongBrowserModel
  12. {
  13. public static String LastSelectedLevelId { get; set; }
  14. private Logger _log = new Logger("SongBrowserModel");
  15. private SongBrowserSettings _settings;
  16. private List<StandardLevelSO> _sortedSongs;
  17. private List<StandardLevelSO> _originalSongs;
  18. private Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> _levelIdToCustomLevel;
  19. private SongLoaderPlugin.OverrideClasses.CustomLevelCollectionSO _gameplayModeCollection;
  20. private Dictionary<String, double> _cachedLastWriteTimes;
  21. public bool InvertingResults { get; private set; }
  22. public SongBrowserSettings Settings
  23. {
  24. get
  25. {
  26. return _settings;
  27. }
  28. }
  29. public List<StandardLevelSO> SortedSongList
  30. {
  31. get
  32. {
  33. return _sortedSongs;
  34. }
  35. }
  36. public Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> LevelIdToCustomSongInfos
  37. {
  38. get
  39. {
  40. return _levelIdToCustomLevel;
  41. }
  42. }
  43. /// <summary>
  44. /// Constructor.
  45. /// </summary>
  46. public SongBrowserModel()
  47. {
  48. _cachedLastWriteTimes = new Dictionary<String, double>();
  49. }
  50. /// <summary>
  51. /// Init this model.
  52. /// </summary>
  53. /// <param name="songSelectionMasterView"></param>
  54. /// <param name="songListViewController"></param>
  55. public void Init()
  56. {
  57. _settings = SongBrowserSettings.Load();
  58. _log.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode);
  59. }
  60. /// <summary>
  61. ///
  62. /// </summary>
  63. public void ToggleInverting()
  64. {
  65. this.InvertingResults = !this.InvertingResults;
  66. }
  67. /// <summary>
  68. /// Get the song cache from the game.
  69. /// TODO: This might not even be necessary anymore. Need to test interactions with BeatSaverDownloader.
  70. /// </summary>
  71. public void UpdateSongLists(GameplayMode gameplayMode)
  72. {
  73. String customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  74. String cachedSongsPath = Path.Combine(customSongsPath, ".cache");
  75. DateTime currentLastWriteTIme = File.GetLastWriteTimeUtc(customSongsPath);
  76. IEnumerable<string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories);
  77. // Get LastWriteTimes
  78. var Epoch = new DateTime(1970, 1, 1);
  79. foreach (string dir in directories)
  80. {
  81. // Flip slashes, match SongLoaderPlugin
  82. string slashed_dir = dir.Replace("\\", "/");
  83. //_log.Debug("Fetching LastWriteTime for {0}", slashed_dir);
  84. _cachedLastWriteTimes[slashed_dir] = (File.GetLastWriteTimeUtc(dir) - Epoch).TotalMilliseconds;
  85. }
  86. // Update song Infos
  87. this.UpdateSongInfos(gameplayMode);
  88. this.ProcessSongList(gameplayMode);
  89. }
  90. /// <summary>
  91. /// Get the song infos from SongLoaderPluging
  92. /// </summary>
  93. private void UpdateSongInfos(GameplayMode gameplayMode)
  94. {
  95. _log.Trace("UpdateSongInfos for Gameplay Mode {0}", gameplayMode);
  96. SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes collections = SongLoaderPlugin.SongLoader.Instance.GetPrivateField<SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes>("_customLevelCollectionsForGameplayModes");
  97. _gameplayModeCollection = collections.GetCollection(gameplayMode) as SongLoaderPlugin.OverrideClasses.CustomLevelCollectionSO;
  98. _originalSongs = collections.GetLevels(gameplayMode).ToList();
  99. _sortedSongs = _originalSongs;
  100. _levelIdToCustomLevel = SongLoader.CustomLevels.ToDictionary(x => x.levelID, x => x);
  101. _log.Debug("Song Browser knows about {0} songs from SongLoader...", _sortedSongs.Count);
  102. }
  103. /// <summary>
  104. /// Sort the song list based on the settings.
  105. /// </summary>
  106. private void ProcessSongList(GameplayMode gameplayMode)
  107. {
  108. _log.Trace("ProcessSongList()");
  109. // Weights used for keeping the original songs in order
  110. // Invert the weights from the game so we can order by descending and make LINQ work with us...
  111. /* Level4, Level2, Level9, Level5, Level10, Level6, Level7, Level1, Level3, Level8, Level11 */
  112. Dictionary<string, int> weights = new Dictionary<string, int>
  113. {
  114. ["Level4"] = 11,
  115. ["Level2"] = 10,
  116. ["Level9"] = 9,
  117. ["Level5"] = 8,
  118. ["Level10"] = 7,
  119. ["Level6"] = 6,
  120. ["Level7"] = 5,
  121. ["Level1"] = 4,
  122. ["Level3"] = 3,
  123. ["Level8"] = 2,
  124. ["Level11"] = 1
  125. };
  126. // This has come in handy many times for debugging issues with Newest.
  127. /*foreach (StandardLevelSO level in _originalSongs)
  128. {
  129. if (_levelIdToCustomLevel.ContainsKey(level.levelID))
  130. {
  131. _log.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID);
  132. }
  133. else
  134. {
  135. _log.Debug("Missing KEY: {0}", level.levelID);
  136. }
  137. }*/
  138. PlayerDynamicData playerData = GameDataModel.instance.gameDynamicData.GetCurrentPlayerDynamicData();
  139. Stopwatch stopwatch = Stopwatch.StartNew();
  140. switch (_settings.sortMode)
  141. {
  142. case SongSortMode.Favorites:
  143. _log.Info("Sorting song list as favorites");
  144. _sortedSongs = _originalSongs
  145. .AsQueryable()
  146. .OrderBy(x => _settings.favorites.Contains(x.levelID) == false)
  147. .ThenBy(x => x.songName)
  148. .ThenBy(x => x.songAuthorName)
  149. .ToList();
  150. break;
  151. case SongSortMode.Original:
  152. _log.Info("Sorting song list as original");
  153. _sortedSongs = _originalSongs
  154. .AsQueryable()
  155. .OrderByDescending(x => weights.ContainsKey(x.levelID) ? weights[x.levelID] : 0)
  156. .ThenBy(x => x.songName)
  157. .ToList();
  158. break;
  159. case SongSortMode.Newest:
  160. _log.Info("Sorting song list as newest.");
  161. _sortedSongs = _originalSongs
  162. .AsQueryable()
  163. .OrderBy(x => weights.ContainsKey(x.levelID) ? weights[x.levelID] : 0)
  164. .ThenByDescending(x => x.levelID.StartsWith("Level") ? weights[x.levelID] : _cachedLastWriteTimes[_levelIdToCustomLevel[x.levelID].customSongInfo.path])
  165. .ToList();
  166. break;
  167. case SongSortMode.Author:
  168. _log.Info("Sorting song list by author");
  169. _sortedSongs = _originalSongs
  170. .AsQueryable()
  171. .OrderBy(x => x.songAuthorName)
  172. .ThenBy(x => x.songName)
  173. .ToList();
  174. break;
  175. case SongSortMode.PlayCount:
  176. _log.Info("Sorting song list by playcount");
  177. // Build a map of levelId to sum of all playcounts and sort.
  178. IEnumerable<LevelDifficulty> difficultyIterator = Enum.GetValues(typeof(LevelDifficulty)).Cast<LevelDifficulty>();
  179. Dictionary<string, int> _levelIdToPlayCount = _originalSongs.ToDictionary(x => x.levelID, x => difficultyIterator.Sum(difficulty => playerData.GetPlayerLevelStatsData(x.levelID, difficulty, gameplayMode).playCount));
  180. _sortedSongs = _originalSongs
  181. .AsQueryable()
  182. .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
  183. .ThenBy(x => x.songName)
  184. .ToList();
  185. break;
  186. case SongSortMode.Random:
  187. _log.Info("Sorting song list by random");
  188. System.Random rnd = new System.Random(Guid.NewGuid().GetHashCode());
  189. _sortedSongs = _originalSongs
  190. .AsQueryable()
  191. .OrderBy(x => rnd.Next())
  192. .ToList();
  193. break;
  194. case SongSortMode.Search:
  195. // Make sure we can actually search.
  196. if (this._settings.searchTerms.Count <= 0)
  197. {
  198. _log.Error("Tried to search for a song with no valid search terms...");
  199. break;
  200. }
  201. string searchTerm = this._settings.searchTerms[0];
  202. if (String.IsNullOrEmpty(searchTerm))
  203. {
  204. _log.Error("Empty search term entered.");
  205. break;
  206. }
  207. _log.Info("Sorting song list by search term: {0}", searchTerm);
  208. //_originalSongs.ForEach(x => _log.Debug($"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()).ToString()));
  209. _sortedSongs = _originalSongs
  210. .AsQueryable()
  211. .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()))
  212. .ToList();
  213. break;
  214. case SongSortMode.Default:
  215. default:
  216. _log.Info("Sorting song list as default (songName)");
  217. _sortedSongs = _originalSongs
  218. .AsQueryable()
  219. .OrderBy(x => x.songName)
  220. .ThenBy(x => x.songAuthorName)
  221. .ToList();
  222. break;
  223. }
  224. if (this.InvertingResults && _settings.sortMode != SongSortMode.Random)
  225. {
  226. _sortedSongs.Reverse();
  227. }
  228. stopwatch.Stop();
  229. _log.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
  230. }
  231. }
  232. }