SongBrowserModel.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. using SongBrowserPlugin.DataAccess;
  2. using SongBrowserPlugin.UI;
  3. using SongLoaderPlugin;
  4. using SongLoaderPlugin.OverrideClasses;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text.RegularExpressions;
  11. using UnityEngine;
  12. namespace SongBrowserPlugin
  13. {
  14. class FolderBeatMapData : BeatmapData
  15. {
  16. public FolderBeatMapData(BeatmapLineData[] beatmapLinesData, BeatmapEventData[] beatmapEventData) :
  17. base(beatmapLinesData, beatmapEventData)
  18. {
  19. }
  20. }
  21. class FolderBeatMapDataSO : BeatmapDataSO
  22. {
  23. public FolderBeatMapDataSO()
  24. {
  25. BeatmapLineData lineData = new BeatmapLineData();
  26. lineData.beatmapObjectsData = new BeatmapObjectData[0];
  27. this._beatmapData = new FolderBeatMapData(
  28. new BeatmapLineData[1]
  29. {
  30. lineData
  31. },
  32. new BeatmapEventData[1]
  33. {
  34. new BeatmapEventData(0, BeatmapEventType.Event0, 0)
  35. });
  36. }
  37. }
  38. class FolderLevel : StandardLevelSO
  39. {
  40. public void Init(String relativePath, String name, Sprite coverImage)
  41. {
  42. _songName = name;
  43. _songSubName = "";
  44. _songAuthorName = "Folder";
  45. _levelID = $"Folder_{relativePath}";
  46. var beatmapData = new FolderBeatMapDataSO();
  47. var difficultyBeatmaps = new List<CustomLevel.CustomDifficultyBeatmap>();
  48. var newDiffBeatmap = new CustomLevel.CustomDifficultyBeatmap(this, LevelDifficulty.Easy, 0, 0, beatmapData);
  49. difficultyBeatmaps.Add(newDiffBeatmap);
  50. var sceneInfo = Resources.Load<SceneInfo>("SceneInfo/" + "DefaultEnvironment" + "SceneInfo");
  51. this.InitFull(_levelID, _songName, _songSubName, _songAuthorName, SongLoaderPlugin.SongLoader.TemporaryAudioClip, 1, 1, 1, 1, 1, 1, 1, coverImage, difficultyBeatmaps.ToArray(), sceneInfo);
  52. this.InitData();
  53. }
  54. }
  55. class DirectoryNode
  56. {
  57. public string Key { get; private set; }
  58. public Dictionary<String, DirectoryNode> Nodes;
  59. public List<StandardLevelSO> Levels;
  60. public DirectoryNode(String key)
  61. {
  62. Key = key;
  63. Nodes = new Dictionary<string, DirectoryNode>();
  64. Levels = new List<StandardLevelSO>();
  65. }
  66. }
  67. public class SongBrowserModel
  68. {
  69. private const String CUSTOM_SONGS_DIR = "CustomSongs";
  70. private DateTime EPOCH = new DateTime(1970, 1, 1);
  71. private Logger _log = new Logger("SongBrowserModel");
  72. // song_browser_settings.xml
  73. private SongBrowserSettings _settings;
  74. // song list management
  75. private List<StandardLevelSO> _filteredSongs;
  76. private List<StandardLevelSO> _sortedSongs;
  77. private List<StandardLevelSO> _originalSongs;
  78. private Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> _levelIdToCustomLevel;
  79. private SongLoaderPlugin.OverrideClasses.CustomLevelCollectionSO _gameplayModeCollection;
  80. private Dictionary<String, double> _cachedLastWriteTimes;
  81. private Dictionary<string, int> _weights;
  82. private Dictionary<String, DirectoryNode> _directoryTree;
  83. private Stack<DirectoryNode> _directoryStack = new Stack<DirectoryNode>();
  84. private GameplayMode _currentGamePlayMode;
  85. /// <summary>
  86. /// Toggle whether inverting the results.
  87. /// </summary>
  88. public bool InvertingResults { get; private set; }
  89. /// <summary>
  90. /// Get the settings the model is using.
  91. /// </summary>
  92. public SongBrowserSettings Settings
  93. {
  94. get
  95. {
  96. return _settings;
  97. }
  98. }
  99. /// <summary>
  100. /// Get the sorted song list for the current working directory.
  101. /// </summary>
  102. public List<StandardLevelSO> SortedSongList
  103. {
  104. get
  105. {
  106. return _sortedSongs;
  107. }
  108. }
  109. /// <summary>
  110. /// Map LevelID to Custom Level info.
  111. /// </summary>
  112. public Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> LevelIdToCustomSongInfos
  113. {
  114. get
  115. {
  116. return _levelIdToCustomLevel;
  117. }
  118. }
  119. /// <summary>
  120. /// How deep is the directory stack.
  121. /// </summary>
  122. public int DirStackSize
  123. {
  124. get
  125. {
  126. return _directoryStack.Count;
  127. }
  128. }
  129. /// <summary>
  130. /// Get the last selected (stored in settings) level id.
  131. /// </summary>
  132. public String LastSelectedLevelId
  133. {
  134. get
  135. {
  136. return _settings.currentLevelId;
  137. }
  138. set
  139. {
  140. _settings.currentLevelId = value;
  141. _settings.Save();
  142. }
  143. }
  144. /// <summary>
  145. /// Get the last known directory the user visited.
  146. /// </summary>
  147. public String CurrentDirectory
  148. {
  149. get
  150. {
  151. return _settings.currentDirectory;
  152. }
  153. set
  154. {
  155. _settings.currentDirectory = value;
  156. _settings.Save();
  157. }
  158. }
  159. private Playlist _currentPlaylist;
  160. /// <summary>
  161. /// Manage the current playlist if one exists.
  162. /// </summary>
  163. public Playlist CurrentPlaylist
  164. {
  165. get
  166. {
  167. if (_currentPlaylist == null)
  168. {
  169. _currentPlaylist = PlaylistsReader.ParsePlaylist(this._settings.currentPlaylistFile);
  170. }
  171. return _currentPlaylist;
  172. }
  173. set
  174. {
  175. _settings.currentPlaylistFile = value.playlistPath;
  176. _currentPlaylist = value;
  177. }
  178. }
  179. /// <summary>
  180. /// Constructor.
  181. /// </summary>
  182. public SongBrowserModel()
  183. {
  184. _cachedLastWriteTimes = new Dictionary<String, double>();
  185. // Weights used for keeping the original songs in order
  186. // Invert the weights from the game so we can order by descending and make LINQ work with us...
  187. /* Level4, Level2, Level9, Level5, Level10, Level6, Level7, Level1, Level3, Level8, Level11 */
  188. _weights = new Dictionary<string, int>
  189. {
  190. ["Level4"] = 11,
  191. ["Level2"] = 10,
  192. ["Level9"] = 9,
  193. ["Level5"] = 8,
  194. ["Level10"] = 7,
  195. ["Level6"] = 6,
  196. ["Level7"] = 5,
  197. ["Level1"] = 4,
  198. ["Level3"] = 3,
  199. ["Level8"] = 2,
  200. ["Level11"] = 1,
  201. ["Level4OneSaber"] = 12,
  202. ["Level1OneSaber"] = 11,
  203. ["Level2OneSaber"] = 10,
  204. ["Level9OneSaber"] = 9,
  205. ["Level7OneSaber"] = 8,
  206. };
  207. }
  208. /// <summary>
  209. /// Init this model.
  210. /// </summary>
  211. /// <param name="songSelectionMasterView"></param>
  212. /// <param name="songListViewController"></param>
  213. public void Init()
  214. {
  215. _settings = SongBrowserSettings.Load();
  216. _log.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode);
  217. }
  218. /// <summary>
  219. /// Easy invert of toggling.
  220. /// </summary>
  221. public void ToggleInverting()
  222. {
  223. this.InvertingResults = !this.InvertingResults;
  224. }
  225. /// <summary>
  226. /// Get the song cache from the game.
  227. /// TODO: This might not even be necessary anymore. Need to test interactions with BeatSaverDownloader.
  228. /// </summary>
  229. public void UpdateSongLists(GameplayMode gameplayMode)
  230. {
  231. _currentGamePlayMode = gameplayMode;
  232. String customSongsPath = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
  233. String cachedSongsPath = Path.Combine(customSongsPath, ".cache");
  234. DateTime currentLastWriteTIme = File.GetLastWriteTimeUtc(customSongsPath);
  235. IEnumerable<string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories);
  236. // Get LastWriteTimes
  237. foreach (var level in SongLoader.CustomLevels)
  238. {
  239. //_log.Debug("Fetching LastWriteTime for {0}", slashed_dir);
  240. _cachedLastWriteTimes[level.levelID] = (File.GetLastWriteTimeUtc(level.customSongInfo.path) - EPOCH).TotalMilliseconds;
  241. }
  242. // Update song Infos, directory tree, and sort
  243. this.UpdateSongInfos(_currentGamePlayMode);
  244. this.UpdateDirectoryTree(customSongsPath);
  245. this.ProcessSongList();
  246. }
  247. /// <summary>
  248. /// Get the song infos from SongLoaderPluging
  249. /// </summary>
  250. private void UpdateSongInfos(GameplayMode gameplayMode)
  251. {
  252. _log.Trace("UpdateSongInfos for Gameplay Mode {0}", gameplayMode);
  253. // Get the level collection from song loader
  254. SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes collections = SongLoaderPlugin.SongLoader.Instance.GetPrivateField<SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes>("_customLevelCollectionsForGameplayModes");
  255. _gameplayModeCollection = collections.GetCollection(gameplayMode) as SongLoaderPlugin.OverrideClasses.CustomLevelCollectionSO;
  256. _originalSongs = collections.GetLevels(gameplayMode).ToList();
  257. _sortedSongs = _originalSongs;
  258. _levelIdToCustomLevel = new Dictionary<string, SongLoaderPlugin.OverrideClasses.CustomLevel>();
  259. foreach (var level in SongLoader.CustomLevels)
  260. {
  261. if (!_levelIdToCustomLevel.Keys.Contains(level.levelID))
  262. _levelIdToCustomLevel.Add(level.levelID, level);
  263. }
  264. _log.Debug("Song Browser knows about {0} songs from SongLoader...", _originalSongs.Count);
  265. }
  266. /// <summary>
  267. /// Make the directory tree.
  268. /// </summary>
  269. /// <param name="customSongsPath"></param>
  270. private void UpdateDirectoryTree(String customSongsPath)
  271. {
  272. // Determine folder mapping
  273. Uri customSongDirUri = new Uri(customSongsPath);
  274. _directoryTree = new Dictionary<string, DirectoryNode>();
  275. _directoryTree[CUSTOM_SONGS_DIR] = new DirectoryNode(CUSTOM_SONGS_DIR);
  276. if (_settings.folderSupportEnabled)
  277. {
  278. foreach (StandardLevelSO level in _originalSongs)
  279. {
  280. AddItemToDirectoryTree(customSongDirUri, level);
  281. }
  282. }
  283. else
  284. {
  285. _directoryTree[CUSTOM_SONGS_DIR].Levels = _originalSongs;
  286. }
  287. // Determine starting location
  288. DirectoryNode currentNode = _directoryTree[CUSTOM_SONGS_DIR];
  289. _directoryStack.Push(currentNode);
  290. // Try to navigate directory path
  291. if (!String.IsNullOrEmpty(this.CurrentDirectory))
  292. {
  293. String[] paths = this.CurrentDirectory.Split('/');
  294. for (int i = 1; i < paths.Length; i++)
  295. {
  296. if (currentNode.Nodes.ContainsKey(paths[i]))
  297. {
  298. currentNode = currentNode.Nodes[paths[i]];
  299. _directoryStack.Push(currentNode);
  300. }
  301. }
  302. }
  303. //PrintDirectory(_directoryTree[CUSTOM_SONGS_DIR], 1);
  304. }
  305. /// <summary>
  306. /// Add a song to directory tree. Determine its place in the tree by walking the split directory path.
  307. /// </summary>
  308. /// <param name="customSongDirUri"></param>
  309. /// <param name="level"></param>
  310. private void AddItemToDirectoryTree(Uri customSongDirUri, StandardLevelSO level)
  311. {
  312. //_log.Debug("Processing item into directory tree: {0}", level.levelID);
  313. DirectoryNode currentNode = _directoryTree[CUSTOM_SONGS_DIR];
  314. // Just add original songs to root and bail
  315. if (level.levelID.Length < 32)
  316. {
  317. currentNode.Levels.Add(level);
  318. return;
  319. }
  320. CustomSongInfo songInfo = _levelIdToCustomLevel[level.levelID].customSongInfo;
  321. Uri customSongUri = new Uri(songInfo.path);
  322. Uri pathDiff = customSongDirUri.MakeRelativeUri(customSongUri);
  323. string relPath = Uri.UnescapeDataString(pathDiff.OriginalString);
  324. string[] paths = relPath.Split('/');
  325. Sprite folderIcon = Base64Sprites.Base64ToSprite(Base64Sprites.FolderIcon);
  326. // Prevent cache directory from building into the tree, will add all its leafs to root.
  327. bool forceIntoRoot = false;
  328. //_log.Debug("Processing path: {0}", songInfo.path);
  329. if (paths.Length > 2)
  330. {
  331. forceIntoRoot = paths[1].Contains(".cache");
  332. Regex r = new Regex(@"^\d{1,}-\d{1,}");
  333. if (r.Match(paths[1]).Success)
  334. {
  335. forceIntoRoot = true;
  336. }
  337. }
  338. for (int i = 1; i < paths.Length; i++)
  339. {
  340. string path = paths[i];
  341. if (path == Path.GetFileName(songInfo.path))
  342. {
  343. //_log.Debug("\tLevel Found Adding {0}->{1}", currentNode.Key, level.levelID);
  344. currentNode.Levels.Add(level);
  345. break;
  346. }
  347. else if (currentNode.Nodes.ContainsKey(path))
  348. {
  349. currentNode = currentNode.Nodes[path];
  350. }
  351. else if (!forceIntoRoot)
  352. {
  353. currentNode.Nodes[path] = new DirectoryNode(path);
  354. FolderLevel folderLevel = new FolderLevel();
  355. folderLevel.Init(relPath, path, folderIcon);
  356. //_log.Debug("\tAdding folder level {0}->{1}", currentNode.Key, path);
  357. currentNode.Levels.Add(folderLevel);
  358. _cachedLastWriteTimes[folderLevel.levelID] = (File.GetLastWriteTimeUtc(relPath) - EPOCH).TotalMilliseconds;
  359. currentNode = currentNode.Nodes[path];
  360. }
  361. }
  362. }
  363. /// <summary>
  364. /// Push a dir onto the stack.
  365. /// </summary>
  366. public void PushDirectory(IStandardLevel level)
  367. {
  368. DirectoryNode currentNode = _directoryStack.Peek();
  369. _log.Debug("Pushing directory {0}", level.songName);
  370. if (!currentNode.Nodes.ContainsKey(level.songName))
  371. {
  372. _log.Debug("Trying to push a directory that doesn't exist at this level.");
  373. return;
  374. }
  375. _directoryStack.Push(currentNode.Nodes[level.songName]);
  376. this.CurrentDirectory = level.levelID;
  377. ProcessSongList();
  378. }
  379. /// <summary>
  380. /// Pop a dir off the stack.
  381. /// </summary>
  382. public void PopDirectory()
  383. {
  384. if (_directoryStack.Count > 1)
  385. {
  386. _directoryStack.Pop();
  387. String currentDir = "";
  388. foreach (DirectoryNode node in _directoryStack)
  389. {
  390. currentDir = node.Key + "/" + currentDir;
  391. }
  392. this.CurrentDirectory = "Folder_" + currentDir;
  393. ProcessSongList();
  394. }
  395. }
  396. /// <summary>
  397. /// Print the directory structure parsed.
  398. /// </summary>
  399. /// <param name="node"></param>
  400. /// <param name="depth"></param>
  401. private void PrintDirectory(DirectoryNode node, int depth)
  402. {
  403. Console.WriteLine("Dir: {0}".PadLeft(depth*4, ' '), node.Key);
  404. node.Levels.ForEach(x => Console.WriteLine("{0}".PadLeft((depth + 1)*4, ' '), x.levelID));
  405. foreach (KeyValuePair<string, DirectoryNode> childNode in node.Nodes)
  406. {
  407. PrintDirectory(childNode.Value, depth + 1);
  408. }
  409. }
  410. /// <summary>
  411. /// Sort the song list based on the settings.
  412. /// </summary>
  413. private void ProcessSongList()
  414. {
  415. _log.Trace("ProcessSongList()");
  416. // This has come in handy many times for debugging issues with Newest.
  417. /*foreach (StandardLevelSO level in _originalSongs)
  418. {
  419. if (_levelIdToCustomLevel.ContainsKey(level.levelID))
  420. {
  421. _log.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID);
  422. }
  423. else
  424. {
  425. _log.Debug("Missing KEY: {0}", level.levelID);
  426. }
  427. }*/
  428. // Playlist filter will load the original songs.
  429. if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null)
  430. {
  431. _originalSongs = null;
  432. }
  433. else
  434. {
  435. _log.Debug("Showing songs for directory: {0}", _directoryStack.Peek().Key);
  436. _originalSongs = _directoryStack.Peek().Levels;
  437. }
  438. // filter
  439. _log.Debug("Starting filtering songs...");
  440. Stopwatch stopwatch = Stopwatch.StartNew();
  441. switch (_settings.filterMode)
  442. {
  443. case SongFilterMode.Favorites:
  444. FilterFavorites(_originalSongs);
  445. break;
  446. case SongFilterMode.Search:
  447. FilterSearch(_originalSongs);
  448. break;
  449. case SongFilterMode.Playlist:
  450. FilterPlaylist();
  451. break;
  452. case SongFilterMode.None:
  453. default:
  454. _log.Info("No song filter selected...");
  455. _filteredSongs = _originalSongs;
  456. break;
  457. }
  458. stopwatch.Stop();
  459. _log.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds);
  460. // sort
  461. _log.Debug("Starting to sort songs...");
  462. stopwatch = Stopwatch.StartNew();
  463. switch (_settings.sortMode)
  464. {
  465. case SongSortMode.Original:
  466. SortOriginal(_filteredSongs);
  467. break;
  468. case SongSortMode.Newest:
  469. SortNewest(_filteredSongs);
  470. break;
  471. case SongSortMode.Author:
  472. SortAuthor(_filteredSongs);
  473. break;
  474. case SongSortMode.PlayCount:
  475. SortPlayCount(_filteredSongs, _currentGamePlayMode);
  476. break;
  477. case SongSortMode.Difficulty:
  478. SortDifficulty(_filteredSongs);
  479. break;
  480. case SongSortMode.Random:
  481. SortRandom(_filteredSongs);
  482. break;
  483. case SongSortMode.Default:
  484. default:
  485. SortSongName(_filteredSongs);
  486. break;
  487. }
  488. if (this.InvertingResults && _settings.sortMode != SongSortMode.Random)
  489. {
  490. _sortedSongs.Reverse();
  491. }
  492. stopwatch.Stop();
  493. _log.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
  494. //_sortedSongs.ForEach(x => _log.Debug(x.levelID));
  495. }
  496. private void FilterFavorites(List<StandardLevelSO> levels)
  497. {
  498. _log.Info("Filtering song list as favorites");
  499. _filteredSongs = levels
  500. .Where(x => _settings.Favorites.Contains(x.levelID))
  501. .ToList();
  502. }
  503. private void FilterSearch(List<StandardLevelSO> levels)
  504. {
  505. // Make sure we can actually search.
  506. if (this._settings.searchTerms.Count <= 0)
  507. {
  508. _log.Error("Tried to search for a song with no valid search terms...");
  509. SortSongName(levels);
  510. return;
  511. }
  512. string searchTerm = this._settings.searchTerms[0];
  513. if (String.IsNullOrEmpty(searchTerm))
  514. {
  515. _log.Error("Empty search term entered.");
  516. SortSongName(levels);
  517. return;
  518. }
  519. _log.Info("Filtering song list by search term: {0}", searchTerm);
  520. //_originalSongs.ForEach(x => _log.Debug($"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()).ToString()));
  521. _filteredSongs = levels
  522. .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()))
  523. .ToList();
  524. }
  525. private void FilterPlaylist()
  526. {
  527. // bail if no playlist, usually means the settings stored one the user then moved.
  528. if (this.CurrentPlaylist == null)
  529. {
  530. _log.Error("Trying to load a null playlist...");
  531. _filteredSongs = _originalSongs;
  532. return;
  533. }
  534. _log.Debug("Filtering songs for playlist: {0}", this.CurrentPlaylist);
  535. List<String> playlistNameListOrdered = this.CurrentPlaylist.songs.Select(x => x.songName).Distinct().ToList();
  536. Dictionary<String, int> songNameToIndex = playlistNameListOrdered.Select((val, index) => new { Index = index, Value = val }).ToDictionary(i => i.Value, i => i.Index);
  537. HashSet<String> songNames = new HashSet<String>(playlistNameListOrdered);
  538. SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes collections = SongLoaderPlugin.SongLoader.Instance.GetPrivateField<SongLoaderPlugin.OverrideClasses.CustomLevelCollectionsForGameplayModes>("_customLevelCollectionsForGameplayModes");
  539. List<StandardLevelSO> songList = collections.GetLevels(_currentGamePlayMode).Where(x => songNames.Contains(x.songName)).ToList();
  540. _log.Debug("\tMatching songs found for playlist: {0}", songList.Count);
  541. _originalSongs = songList;
  542. _filteredSongs = songList
  543. .OrderBy(x => songNameToIndex[x.songName])
  544. .ToList();
  545. }
  546. private void SortOriginal(List<StandardLevelSO> levels)
  547. {
  548. _log.Info("Sorting song list as original");
  549. _sortedSongs = levels;/*levels
  550. .AsQueryable()
  551. .OrderByDescending(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
  552. .ThenBy(x => x.songName)
  553. .ToList();*/
  554. }
  555. private void SortNewest(List<StandardLevelSO> levels)
  556. {
  557. _log.Info("Sorting song list as newest.");
  558. _sortedSongs = levels
  559. .OrderBy(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
  560. .ThenByDescending(x => x.levelID.StartsWith("Level") ? _weights[x.levelID] : _cachedLastWriteTimes[x.levelID])
  561. .ToList();
  562. }
  563. private void SortAuthor(List<StandardLevelSO> levels)
  564. {
  565. _log.Info("Sorting song list by author");
  566. _sortedSongs = levels
  567. .OrderBy(x => x.songAuthorName)
  568. .ThenBy(x => x.songName)
  569. .ToList();
  570. }
  571. private void SortPlayCount(List<StandardLevelSO> levels, GameplayMode gameplayMode)
  572. {
  573. _log.Info("Sorting song list by playcount");
  574. // Build a map of levelId to sum of all playcounts and sort.
  575. PlayerDynamicData playerData = GameDataModel.instance.gameDynamicData.GetCurrentPlayerDynamicData();
  576. IEnumerable<LevelDifficulty> difficultyIterator = Enum.GetValues(typeof(LevelDifficulty)).Cast<LevelDifficulty>();
  577. Dictionary<string, int> levelIdToPlayCount = new Dictionary<string, int>();
  578. foreach (var level in levels)
  579. {
  580. if (!levelIdToPlayCount.ContainsKey(level.levelID))
  581. {
  582. // Skip folders
  583. if (level.levelID.StartsWith("Folder_"))
  584. {
  585. levelIdToPlayCount.Add(level.levelID, 0);
  586. }
  587. else
  588. {
  589. int playCountSum = 0;
  590. foreach (LevelDifficulty difficulty in difficultyIterator)
  591. {
  592. PlayerLevelStatsData stats = playerData.GetPlayerLevelStatsData(level.levelID, difficulty, gameplayMode);
  593. playCountSum += stats.playCount;
  594. }
  595. levelIdToPlayCount.Add(level.levelID, playCountSum);
  596. }
  597. }
  598. }
  599. _sortedSongs = levels
  600. .OrderByDescending(x => levelIdToPlayCount[x.levelID])
  601. .ThenBy(x => x.songName)
  602. .ToList();
  603. }
  604. private void SortDifficulty(List<StandardLevelSO> levels)
  605. {
  606. _log.Info("Sorting song list by random");
  607. System.Random rnd = new System.Random(Guid.NewGuid().GetHashCode());
  608. Dictionary<LevelDifficulty, int> difficultyWeights = new Dictionary<LevelDifficulty, int>
  609. {
  610. [LevelDifficulty.Easy] = int.MaxValue - 4,
  611. [LevelDifficulty.Normal] = int.MaxValue - 3,
  612. [LevelDifficulty.Hard] = int.MaxValue - 2,
  613. [LevelDifficulty.Expert] = int.MaxValue - 1,
  614. [LevelDifficulty.ExpertPlus] = int.MaxValue,
  615. };
  616. IEnumerable<LevelDifficulty> difficultyIterator = Enum.GetValues(typeof(LevelDifficulty)).Cast<LevelDifficulty>();
  617. Dictionary<string, int> levelIdToDifficultyValue = new Dictionary<string, int>();
  618. foreach (var level in levels)
  619. {
  620. if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
  621. {
  622. // Skip folders
  623. if (level.levelID.StartsWith("Folder_"))
  624. {
  625. levelIdToDifficultyValue.Add(level.levelID, 0);
  626. }
  627. else
  628. {
  629. int difficultyValue = 0;
  630. foreach (LevelDifficulty difficulty in difficultyIterator)
  631. {
  632. IStandardLevelDifficultyBeatmap beatmap = level.GetDifficultyLevel(difficulty);
  633. if (beatmap != null)
  634. {
  635. difficultyValue += difficultyWeights[difficulty];
  636. break;
  637. }
  638. }
  639. levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
  640. }
  641. }
  642. }
  643. _sortedSongs = levels
  644. .OrderBy(x => levelIdToDifficultyValue[x.levelID])
  645. .ThenBy(x => x.songName)
  646. .ToList();
  647. }
  648. private void SortRandom(List<StandardLevelSO> levels)
  649. {
  650. _log.Info("Sorting song list by random");
  651. System.Random rnd = new System.Random(Guid.NewGuid().GetHashCode());
  652. _sortedSongs = levels
  653. .OrderBy(x => rnd.Next())
  654. .ToList();
  655. }
  656. private void SortSongName(List<StandardLevelSO> levels)
  657. {
  658. _log.Info("Sorting song list as default (songName)");
  659. _sortedSongs = levels
  660. .OrderBy(x => x.songName)
  661. .ThenBy(x => x.songAuthorName)
  662. .ToList();
  663. }
  664. }
  665. }