SongBrowserModel.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  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 UnityEngine;
  11. using Logger = SongBrowserPlugin.Logging.Logger;
  12. namespace SongBrowserPlugin
  13. {
  14. public class SongBrowserModel
  15. {
  16. private const String CUSTOM_SONGS_DIR = "CustomSongs";
  17. private readonly DateTime EPOCH = new DateTime(1970, 1, 1);
  18. // song_browser_settings.xml
  19. private SongBrowserSettings _settings;
  20. // song list management
  21. private double _customSongDirLastWriteTime = 0;
  22. private List<BeatmapLevelSO> _sortedSongs;
  23. private Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> _levelIdToCustomLevel;
  24. private Dictionary<String, double> _cachedLastWriteTimes;
  25. private Dictionary<string, int> _weights;
  26. private Dictionary<BeatmapDifficulty, int> _difficultyWeights;
  27. private Dictionary<string, ScoreSaberData> _levelIdToScoreSaberData = null;
  28. private Dictionary<string, int> _levelIdToPlayCount;
  29. private Dictionary<string, string> _levelIdToSongVersion;
  30. private Dictionary<string, BeatmapLevelSO> _keyToSong;
  31. public BeatmapCharacteristicSO CurrentBeatmapCharacteristicSO;
  32. public static Action<List<CustomLevel>> didFinishProcessingSongs;
  33. private IBeatmapLevelPack _currentLevelPack;
  34. private Dictionary<string, List<BeatmapLevelSO>> _levelPackToSongs;
  35. private bool _isPreviewLevelPack;
  36. /// <summary>
  37. /// Get the settings the model is using.
  38. /// </summary>
  39. public SongBrowserSettings Settings
  40. {
  41. get
  42. {
  43. return _settings;
  44. }
  45. }
  46. /// <summary>
  47. /// Get the sorted song list for the current working directory.
  48. /// </summary>
  49. public List<BeatmapLevelSO> SortedSongList
  50. {
  51. get
  52. {
  53. return _sortedSongs;
  54. }
  55. }
  56. /// <summary>
  57. /// Map LevelID to Custom Level info.
  58. /// </summary>
  59. public Dictionary<String, SongLoaderPlugin.OverrideClasses.CustomLevel> LevelIdToCustomSongInfos
  60. {
  61. get
  62. {
  63. return _levelIdToCustomLevel;
  64. }
  65. }
  66. /// <summary>
  67. /// Map LevelID to score saber data.
  68. /// </summary>
  69. public Dictionary<string, ScoreSaberData> LevelIdToScoreSaberData
  70. {
  71. get
  72. {
  73. return _levelIdToScoreSaberData;
  74. }
  75. }
  76. /// <summary>
  77. /// Get the last selected (stored in settings) level id.
  78. /// </summary>
  79. public String LastSelectedLevelId
  80. {
  81. get
  82. {
  83. return _settings.currentLevelId;
  84. }
  85. set
  86. {
  87. _settings.currentLevelId = value;
  88. _settings.Save();
  89. }
  90. }
  91. private Playlist _currentPlaylist;
  92. /// <summary>
  93. /// Manage the current playlist if one exists.
  94. /// </summary>
  95. public Playlist CurrentPlaylist
  96. {
  97. get
  98. {
  99. if (_currentPlaylist == null)
  100. {
  101. _currentPlaylist = Playlist.LoadPlaylist(this._settings.currentPlaylistFile);
  102. }
  103. return _currentPlaylist;
  104. }
  105. set
  106. {
  107. _settings.currentPlaylistFile = value.fileLoc;
  108. _currentPlaylist = value;
  109. }
  110. }
  111. public bool IsCurrentLevelPackPreview
  112. {
  113. get
  114. {
  115. return _isPreviewLevelPack;
  116. }
  117. }
  118. public IBeatmapLevelPack CurrentLevelPack
  119. {
  120. get
  121. {
  122. return _currentLevelPack;
  123. }
  124. }
  125. public Playlist CurrentEditingPlaylist;
  126. public HashSet<String> CurrentEditingPlaylistLevelIds;
  127. /// <summary>
  128. /// Constructor.
  129. /// </summary>
  130. public SongBrowserModel()
  131. {
  132. _levelIdToCustomLevel = new Dictionary<string, SongLoaderPlugin.OverrideClasses.CustomLevel>();
  133. _cachedLastWriteTimes = new Dictionary<String, double>();
  134. _levelIdToScoreSaberData = new Dictionary<string, ScoreSaberData>();
  135. _levelIdToPlayCount = new Dictionary<string, int>();
  136. _levelIdToSongVersion = new Dictionary<string, string>();
  137. _keyToSong = new Dictionary<string, BeatmapLevelSO>();
  138. _levelPackToSongs = new Dictionary<string, List<BeatmapLevelSO>>();
  139. CurrentEditingPlaylistLevelIds = new HashSet<string>();
  140. // Weights used for keeping the original songs in order
  141. // Invert the weights from the game so we can order by descending and make LINQ work with us...
  142. /* Level4, Level2, Level9, Level5, Level10, Level6, Level7, Level1, Level3, Level8, Level11 */
  143. _weights = new Dictionary<string, int>
  144. {
  145. ["OneHopeLevel"] = 12,
  146. ["100Bills"] = 11,
  147. ["Escape"] = 10,
  148. ["Legend"] = 9,
  149. ["BeatSaber"] = 8,
  150. ["AngelVoices"] = 7,
  151. ["CountryRounds"] = 6,
  152. ["BalearicPumping"] = 5,
  153. ["Breezer"] = 4,
  154. ["CommercialPumping"] = 3,
  155. ["TurnMeOn"] = 2,
  156. ["LvlInsane"] = 1,
  157. ["100BillsOneSaber"] = 12,
  158. ["EscapeOneSaber"] = 11,
  159. ["LegendOneSaber"] = 10,
  160. ["BeatSaberOneSaber"] = 9,
  161. ["CommercialPumpingOneSaber"] = 8,
  162. ["TurnMeOnOneSaber"] = 8,
  163. };
  164. _difficultyWeights = new Dictionary<BeatmapDifficulty, int>
  165. {
  166. [BeatmapDifficulty.Easy] = int.MaxValue - 4,
  167. [BeatmapDifficulty.Normal] = int.MaxValue - 3,
  168. [BeatmapDifficulty.Hard] = int.MaxValue - 2,
  169. [BeatmapDifficulty.Expert] = int.MaxValue - 1,
  170. [BeatmapDifficulty.ExpertPlus] = int.MaxValue,
  171. };
  172. }
  173. /// <summary>
  174. /// Init this model.
  175. /// </summary>
  176. /// <param name="songSelectionMasterView"></param>
  177. /// <param name="songListViewController"></param>
  178. public void Init()
  179. {
  180. _settings = SongBrowserSettings.Load();
  181. Logger.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode);
  182. }
  183. /// <summary>
  184. /// Easy invert of toggling.
  185. /// </summary>
  186. public void ToggleInverting()
  187. {
  188. this.Settings.invertSortResults = !this.Settings.invertSortResults;
  189. }
  190. /// <summary>
  191. /// Get the song cache from the game.
  192. /// </summary>
  193. public void UpdateLevelRecords()
  194. {
  195. // get a default beatmap characteristic...
  196. if (this.CurrentBeatmapCharacteristicSO == null)
  197. {
  198. // TODO - this probably needs to be queried or passed in earlier, hack for now
  199. // StandardBeatmapCharacteristic
  200. Logger.Info("No Beatmap Characteristic selected... selecting default...");
  201. this.CurrentBeatmapCharacteristicSO = Resources.FindObjectsOfTypeAll<BeatmapCharacteristicCollectionSO>().FirstOrDefault().beatmapCharacteristics[0];
  202. }
  203. Stopwatch timer = new Stopwatch();
  204. timer.Start();
  205. BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
  206. foreach (BeatmapLevelPackSO levelPack in levelPacks)
  207. {
  208. Logger.Debug("Attempting to get song list from levelPack: {0}...", levelPack);
  209. var beatmapLevelPack = levelPack as BeatmapLevelPackSO;
  210. // TODO - need to rethink interface here, not all level packs can be cast this high, some sort functions need it.
  211. // - this helps prevent DLC from breaking everything
  212. if (beatmapLevelPack == null)
  213. {
  214. continue;
  215. }
  216. _levelPackToSongs[levelPack.packName] = (beatmapLevelPack.beatmapLevelCollection as BeatmapLevelCollectionSO).GetPrivateField<BeatmapLevelSO[]>("_beatmapLevels").ToList();
  217. Logger.Debug("Got {0} songs from level collections...", _levelPackToSongs[levelPack.packName].Count);
  218. //_levelPackToSongs[levelPack.packName].ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID));
  219. }
  220. // Calculate some information about the custom song dir
  221. String customSongsPath = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
  222. String revSlashCustomSongPath = customSongsPath.Replace('\\', '/');
  223. double currentCustomSongDirLastWriteTIme = (File.GetLastWriteTimeUtc(customSongsPath) - EPOCH).TotalMilliseconds;
  224. bool customSongDirChanged = false;
  225. if (_customSongDirLastWriteTime != currentCustomSongDirLastWriteTIme)
  226. {
  227. customSongDirChanged = true;
  228. _customSongDirLastWriteTime = currentCustomSongDirLastWriteTIme;
  229. }
  230. if (!Directory.Exists(customSongsPath))
  231. {
  232. Logger.Error("CustomSong directory is missing...");
  233. return;
  234. }
  235. IEnumerable<string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories);
  236. // Get LastWriteTimes
  237. Stopwatch lastWriteTimer = new Stopwatch();
  238. lastWriteTimer.Start();
  239. foreach (var level in SongLoader.CustomLevels)
  240. {
  241. // If we already know this levelID, don't both updating it.
  242. // SongLoader should filter duplicates but in case of failure we don't want to crash
  243. if (!_cachedLastWriteTimes.ContainsKey(level.levelID) || customSongDirChanged)
  244. {
  245. // Always use the newest date.
  246. var lastWriteTime = File.GetLastWriteTimeUtc(level.customSongInfo.path);
  247. var lastCreateTime = File.GetCreationTimeUtc(level.customSongInfo.path);
  248. var lastTime = lastWriteTime > lastCreateTime ? lastWriteTime : lastCreateTime;
  249. _cachedLastWriteTimes[level.levelID] = (lastTime - EPOCH).TotalMilliseconds;
  250. }
  251. if (!_levelIdToCustomLevel.ContainsKey(level.levelID))
  252. {
  253. _levelIdToCustomLevel.Add(level.levelID, level);
  254. }
  255. if (!_levelIdToSongVersion.ContainsKey(level.levelID))
  256. {
  257. DirectoryInfo info = new DirectoryInfo(level.customSongInfo.path);
  258. string currentDirectoryName = info.Name;
  259. String version = level.customSongInfo.path.Replace(revSlashCustomSongPath, "").Replace(currentDirectoryName, "").Replace("/", "");
  260. if (!String.IsNullOrEmpty(version))
  261. {
  262. _levelIdToSongVersion.Add(level.levelID, version);
  263. _keyToSong.Add(version, level);
  264. }
  265. }
  266. }
  267. lastWriteTimer.Stop();
  268. Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds);
  269. // Update song Infos, directory tree, and sort
  270. this.UpdateScoreSaberDataMapping();
  271. this.UpdatePlayCounts();
  272. // Check if we need to upgrade settings file favorites
  273. try
  274. {
  275. this.Settings.ConvertFavoritesToPlaylist(_levelIdToCustomLevel, _levelIdToSongVersion);
  276. }
  277. catch (Exception e)
  278. {
  279. Logger.Exception("FAILED TO CONVERT FAVORITES TO PLAYLIST!", e);
  280. }
  281. // load the current editing playlist or make one
  282. if (!String.IsNullOrEmpty(this.Settings.currentEditingPlaylistFile))
  283. {
  284. CurrentEditingPlaylist = Playlist.LoadPlaylist(this.Settings.currentEditingPlaylistFile);
  285. }
  286. if (CurrentEditingPlaylist == null)
  287. {
  288. CurrentEditingPlaylist = new Playlist
  289. {
  290. playlistTitle = "Song Browser Favorites",
  291. playlistAuthor = "SongBrowserPlugin",
  292. fileLoc = this.Settings.currentEditingPlaylistFile,
  293. image = Base64Sprites.PlaylistIconB64,
  294. songs = new List<PlaylistSong>(),
  295. };
  296. }
  297. CurrentEditingPlaylistLevelIds = new HashSet<string>();
  298. foreach (PlaylistSong ps in CurrentEditingPlaylist.songs)
  299. {
  300. CurrentEditingPlaylistLevelIds.Add(ps.levelId);
  301. }
  302. // Actually sort and filter
  303. this.ProcessSongList();
  304. // Signal complete
  305. if (SongLoader.CustomLevels.Count > 0)
  306. {
  307. didFinishProcessingSongs?.Invoke(SongLoader.CustomLevels);
  308. }
  309. timer.Stop();
  310. Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds);
  311. }
  312. /// <summary>
  313. /// Update the gameplay play counts.
  314. /// </summary>
  315. /// <param name="gameplayMode"></param>
  316. private void UpdatePlayCounts()
  317. {
  318. // Build a map of levelId to sum of all playcounts and sort.
  319. PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();
  320. IEnumerable<BeatmapDifficulty> difficultyIterator = Enum.GetValues(typeof(BeatmapDifficulty)).Cast<BeatmapDifficulty>();
  321. foreach (KeyValuePair<string, List<BeatmapLevelSO>> entry in _levelPackToSongs)
  322. {
  323. foreach (var level in entry.Value)
  324. {
  325. if (!_levelIdToPlayCount.ContainsKey(level.levelID))
  326. {
  327. // Skip folders
  328. int playCountSum = 0;
  329. foreach (BeatmapDifficulty difficulty in difficultyIterator)
  330. {
  331. PlayerLevelStatsData stats = playerData.currentLocalPlayer.GetPlayerLevelStatsData(level.levelID, difficulty, this.CurrentBeatmapCharacteristicSO);
  332. playCountSum += stats.playCount;
  333. }
  334. _levelIdToPlayCount.Add(level.levelID, playCountSum);
  335. }
  336. }
  337. }
  338. }
  339. /// <summary>
  340. /// Parse the current pp data file.
  341. /// Public so controllers can decide when to update it.
  342. /// </summary>
  343. public void UpdateScoreSaberDataMapping()
  344. {
  345. Logger.Trace("UpdateScoreSaberDataMapping()");
  346. ScoreSaberDataFile scoreSaberDataFile = ScoreSaberDatabaseDownloader.ScoreSaberDataFile;
  347. // bail
  348. if (scoreSaberDataFile == null)
  349. {
  350. Logger.Warning("Cannot fetch song difficulty for score saber data...");
  351. return;
  352. }
  353. foreach (var level in SongLoader.CustomLevels)
  354. {
  355. // Skip
  356. if (_levelIdToScoreSaberData.ContainsKey(level.levelID))
  357. {
  358. continue;
  359. }
  360. ScoreSaberData scoreSaberData = null;
  361. // try to version match first
  362. if (_levelIdToSongVersion.ContainsKey(level.levelID))
  363. {
  364. String version = _levelIdToSongVersion[level.levelID];
  365. if (scoreSaberDataFile.SongVersionToScoreSaberData.ContainsKey(version))
  366. {
  367. scoreSaberData = scoreSaberDataFile.SongVersionToScoreSaberData[version];
  368. }
  369. }
  370. if (scoreSaberData != null)
  371. {
  372. //Logger.Debug("{0} = {1}pp", level.songName, pp);
  373. _levelIdToScoreSaberData.Add(level.levelID, scoreSaberData);
  374. }
  375. }
  376. }
  377. /// <summary>
  378. /// Add Song to Editing Playlist
  379. /// </summary>
  380. /// <param name="songInfo"></param>
  381. public void AddSongToEditingPlaylist(IBeatmapLevel songInfo)
  382. {
  383. if (this.CurrentEditingPlaylist == null)
  384. {
  385. return;
  386. }
  387. this.CurrentEditingPlaylist.songs.Add(new PlaylistSong()
  388. {
  389. songName = songInfo.songName,
  390. levelId = songInfo.levelID,
  391. key = _levelIdToSongVersion.ContainsKey(songInfo.levelID) ? _levelIdToSongVersion[songInfo.levelID] : songInfo.levelID,
  392. });
  393. this.CurrentEditingPlaylistLevelIds.Add(songInfo.levelID);
  394. this.CurrentEditingPlaylist.SavePlaylist();
  395. }
  396. /// <summary>
  397. /// Remove Song from editing playlist
  398. /// </summary>
  399. /// <param name="levelId"></param>
  400. public void RemoveSongFromEditingPlaylist(IBeatmapLevel songInfo)
  401. {
  402. if (this.CurrentEditingPlaylist == null)
  403. {
  404. return;
  405. }
  406. this.CurrentEditingPlaylist.songs.RemoveAll(x => x.levelId == songInfo.levelID);
  407. this.CurrentEditingPlaylistLevelIds.Remove(songInfo.levelID);
  408. this.CurrentEditingPlaylist.SavePlaylist();
  409. }
  410. /// <summary>
  411. /// Resets all the level packs back to their original values.
  412. /// </summary>
  413. /// <param name="pack"></param>
  414. private void ResetLevelPacks()
  415. {
  416. Logger.Debug("Setting level packs back to their original values!");
  417. BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
  418. foreach (BeatmapLevelPackSO levelPack in levelPacks)
  419. {
  420. if (!_levelPackToSongs.ContainsKey(levelPack.packName))
  421. {
  422. Logger.Debug("We know nothing about pack: {0}", levelPack.packName);
  423. continue;
  424. }
  425. var levels = _levelPackToSongs[levelPack.packName].ToArray();
  426. ReflectionUtil.SetPrivateField(levelPack.beatmapLevelCollection, "_beatmapLevels", levels);
  427. }
  428. }
  429. /// <summary>
  430. /// Set current level pack, reset all packs just in case.
  431. /// </summary>
  432. /// <param name="pack"></param>
  433. public void SetCurrentLevelPack(IBeatmapLevelPack pack)
  434. {
  435. Logger.Debug("Setting level packs back to their original values!");
  436. this.ResetLevelPacks();
  437. this._currentLevelPack = pack;
  438. var beatmapLevelPack = pack as BeatmapLevelPackSO;
  439. if (beatmapLevelPack == null)
  440. {
  441. Logger.Debug("DLC Detected... Disabling SongBrowser...");
  442. _isPreviewLevelPack = true;
  443. }
  444. else
  445. {
  446. Logger.Debug("Owned level pack... Enabling SongBrowser...");
  447. _isPreviewLevelPack = false;
  448. }
  449. }
  450. /// <summary>
  451. /// Overwrite the current level pack.
  452. /// </summary>
  453. private void OverwriteCurrentLevelPack()
  454. {
  455. Logger.Debug("Overwriting levelPack [{0}] beatmapLevelCollection", this._currentLevelPack);
  456. IBeatmapLevelPack levelPack = this._currentLevelPack;
  457. var levels = _sortedSongs.ToArray();
  458. ReflectionUtil.SetPrivateField(levelPack.beatmapLevelCollection, "_beatmapLevels", levels);
  459. }
  460. /// <summary>
  461. /// Sort the song list based on the settings.
  462. /// </summary>
  463. public void ProcessSongList()
  464. {
  465. Logger.Trace("ProcessSongList()");
  466. // This has come in handy many times for debugging issues with Newest.
  467. /*foreach (BeatmapLevelSO level in _originalSongs)
  468. {
  469. if (_levelIdToCustomLevel.ContainsKey(level.levelID))
  470. {
  471. Logger.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID);
  472. }
  473. else
  474. {
  475. Logger.Debug("Missing KEY: {0}", level.levelID);
  476. }
  477. }*/
  478. // TODO - remove as part of unifying song list interface
  479. if (_isPreviewLevelPack)
  480. {
  481. return;
  482. }
  483. if (_levelPackToSongs.Count == 0 || this._currentLevelPack == null || !this._levelPackToSongs.ContainsKey(this._currentLevelPack.packName))
  484. {
  485. Logger.Debug("Cannot process songs yet, songs infos have not been processed...");
  486. return;
  487. }
  488. // Playlist filter will load the original songs.
  489. List<BeatmapLevelSO> unsortedSongs = null;
  490. List<BeatmapLevelSO> filteredSongs = null;
  491. if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null)
  492. {
  493. unsortedSongs = null;
  494. }
  495. else
  496. {
  497. Logger.Debug("Using songs from level pack: {0}", this._currentLevelPack.packName);
  498. unsortedSongs = new List<BeatmapLevelSO>(_levelPackToSongs[this._currentLevelPack.packName]);
  499. }
  500. // filter
  501. Logger.Debug("Starting filtering songs...");
  502. Stopwatch stopwatch = Stopwatch.StartNew();
  503. switch (_settings.filterMode)
  504. {
  505. case SongFilterMode.Favorites:
  506. filteredSongs = FilterFavorites();
  507. break;
  508. case SongFilterMode.Search:
  509. filteredSongs = FilterSearch(unsortedSongs);
  510. break;
  511. case SongFilterMode.Playlist:
  512. filteredSongs = FilterPlaylist();
  513. break;
  514. case SongFilterMode.None:
  515. default:
  516. Logger.Info("No song filter selected...");
  517. filteredSongs = unsortedSongs;
  518. break;
  519. }
  520. stopwatch.Stop();
  521. Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds);
  522. // sort
  523. Logger.Debug("Starting to sort songs...");
  524. stopwatch = Stopwatch.StartNew();
  525. switch (_settings.sortMode)
  526. {
  527. case SongSortMode.Original:
  528. SortOriginal(filteredSongs);
  529. break;
  530. case SongSortMode.Newest:
  531. SortNewest(filteredSongs);
  532. break;
  533. case SongSortMode.Author:
  534. SortAuthor(filteredSongs);
  535. break;
  536. case SongSortMode.PlayCount:
  537. SortPlayCount(filteredSongs);
  538. break;
  539. case SongSortMode.PP:
  540. SortPerformancePoints(filteredSongs);
  541. break;
  542. case SongSortMode.Difficulty:
  543. SortDifficulty(filteredSongs);
  544. break;
  545. case SongSortMode.Random:
  546. SortRandom(filteredSongs);
  547. break;
  548. case SongSortMode.Default:
  549. default:
  550. SortSongName(filteredSongs);
  551. break;
  552. }
  553. if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random)
  554. {
  555. _sortedSongs.Reverse();
  556. }
  557. stopwatch.Stop();
  558. Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
  559. this.OverwriteCurrentLevelPack();
  560. //_sortedSongs.ForEach(x => Logger.Debug(x.levelID));
  561. }
  562. /// <summary>
  563. /// For now the editing playlist will be considered the favorites playlist.
  564. /// Users can edit the settings file themselves.
  565. /// </summary>
  566. private List<BeatmapLevelSO> FilterFavorites()
  567. {
  568. Logger.Info("Filtering song list as favorites playlist...");
  569. if (this.CurrentEditingPlaylist != null)
  570. {
  571. this.CurrentPlaylist = this.CurrentEditingPlaylist;
  572. }
  573. return this.FilterPlaylist();
  574. }
  575. private List<BeatmapLevelSO> FilterSearch(List<BeatmapLevelSO> levels)
  576. {
  577. // Make sure we can actually search.
  578. if (this._settings.searchTerms.Count <= 0)
  579. {
  580. Logger.Error("Tried to search for a song with no valid search terms...");
  581. SortSongName(levels);
  582. return levels;
  583. }
  584. string searchTerm = this._settings.searchTerms[0];
  585. if (String.IsNullOrEmpty(searchTerm))
  586. {
  587. Logger.Error("Empty search term entered.");
  588. SortSongName(levels);
  589. return levels;
  590. }
  591. Logger.Info("Filtering song list by search term: {0}", searchTerm);
  592. var terms = searchTerm.Split(' ');
  593. foreach (var term in terms)
  594. {
  595. levels = levels.Intersect(
  596. levels
  597. .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(term.ToLower()))
  598. .ToList(
  599. )
  600. ).ToList();
  601. }
  602. return levels;
  603. }
  604. private List<BeatmapLevelSO> FilterPlaylist()
  605. {
  606. // bail if no playlist, usually means the settings stored one the user then moved.
  607. if (this.CurrentPlaylist == null)
  608. {
  609. Logger.Error("Trying to load a null playlist...");
  610. this.Settings.filterMode = SongFilterMode.None;
  611. return null;
  612. }
  613. // Get song keys
  614. PlaylistsCollection.MatchSongsForPlaylist(this.CurrentPlaylist, true);
  615. Logger.Debug("Filtering songs for playlist: {0}", this.CurrentPlaylist.playlistTitle);
  616. Dictionary<String, BeatmapLevelSO> levelDict = new Dictionary<string, BeatmapLevelSO>();
  617. foreach (KeyValuePair<string, List<BeatmapLevelSO>> entry in _levelPackToSongs)
  618. {
  619. foreach (BeatmapLevelSO level in entry.Value)
  620. {
  621. if (!levelDict.ContainsKey(level.levelID))
  622. {
  623. levelDict.Add(level.levelID, level);
  624. }
  625. }
  626. }
  627. List<BeatmapLevelSO> songList = new List<BeatmapLevelSO>();
  628. foreach (PlaylistSong ps in this.CurrentPlaylist.songs)
  629. {
  630. if (ps.level != null)
  631. {
  632. songList.Add(levelDict[ps.level.levelID]);
  633. }
  634. else
  635. {
  636. Logger.Warning("Could not find song in playlist: {0}", ps.songName);
  637. }
  638. }
  639. Logger.Debug("Playlist filtered song count: {0}", songList.Count);
  640. return songList;
  641. }
  642. private void SortOriginal(List<BeatmapLevelSO> levels)
  643. {
  644. Logger.Info("Sorting song list as original");
  645. _sortedSongs = levels;
  646. }
  647. private void SortNewest(List<BeatmapLevelSO> levels)
  648. {
  649. Logger.Info("Sorting song list as newest.");
  650. _sortedSongs = levels
  651. .OrderBy(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
  652. .ThenByDescending(x => !_levelIdToCustomLevel.ContainsKey(x.levelID) ? (_weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0) : _cachedLastWriteTimes[x.levelID])
  653. .ToList();
  654. }
  655. private void SortAuthor(List<BeatmapLevelSO> levels)
  656. {
  657. Logger.Info("Sorting song list by author");
  658. _sortedSongs = levels
  659. .OrderBy(x => x.songAuthorName)
  660. .ThenBy(x => x.songName)
  661. .ToList();
  662. }
  663. private void SortPlayCount(List<BeatmapLevelSO> levels)
  664. {
  665. Logger.Info("Sorting song list by playcount");
  666. _sortedSongs = levels
  667. .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
  668. .ThenBy(x => x.songName)
  669. .ToList();
  670. }
  671. private void SortPerformancePoints(List<BeatmapLevelSO> levels)
  672. {
  673. Logger.Info("Sorting song list by performance points...");
  674. _sortedSongs = levels
  675. .OrderByDescending(x => _levelIdToScoreSaberData.ContainsKey(x.levelID) ? _levelIdToScoreSaberData[x.levelID].maxPp : 0)
  676. .ToList();
  677. }
  678. private void SortDifficulty(List<BeatmapLevelSO> levels)
  679. {
  680. Logger.Info("Sorting song list by difficulty...");
  681. IEnumerable<BeatmapDifficulty> difficultyIterator = Enum.GetValues(typeof(BeatmapDifficulty)).Cast<BeatmapDifficulty>();
  682. Dictionary<string, int> levelIdToDifficultyValue = new Dictionary<string, int>();
  683. foreach (var level in levels)
  684. {
  685. if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
  686. {
  687. int difficultyValue = 0;
  688. // Get the beatmap difficulties
  689. var difficulties = level.difficultyBeatmapSets
  690. .Where(x => x.beatmapCharacteristic == this.CurrentBeatmapCharacteristicSO)
  691. .SelectMany(x => x.difficultyBeatmaps);
  692. foreach (IDifficultyBeatmap difficultyBeatmap in difficulties)
  693. {
  694. difficultyValue += _difficultyWeights[difficultyBeatmap.difficulty];
  695. }
  696. levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
  697. }
  698. }
  699. _sortedSongs = levels
  700. .OrderBy(x => levelIdToDifficultyValue[x.levelID])
  701. .ThenBy(x => x.songName)
  702. .ToList();
  703. _sortedSongs = levels;
  704. }
  705. private void SortRandom(List<BeatmapLevelSO> levels)
  706. {
  707. Logger.Info("Sorting song list by random (seed={0})...", this.Settings.randomSongSeed);
  708. System.Random rnd = new System.Random(this.Settings.randomSongSeed);
  709. _sortedSongs = levels
  710. .OrderBy(x => rnd.Next())
  711. .ToList();
  712. }
  713. private void SortSongName(List<BeatmapLevelSO> levels)
  714. {
  715. Logger.Info("Sorting song list as default (songName)");
  716. _sortedSongs = levels
  717. .OrderBy(x => x.songName)
  718. .ThenBy(x => x.songAuthorName)
  719. .ToList();
  720. }
  721. }
  722. }