SongBrowserModel.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. using SongBrowser.DataAccess;
  2. using SongCore.Utilities;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using TMPro;
  10. using UnityEngine;
  11. using Logger = SongBrowser.Logging.Logger;
  12. namespace SongBrowser
  13. {
  14. public class SongBrowserModel
  15. {
  16. public static readonly string FilteredSongsPackId = CustomLevelLoader.kCustomLevelPackPrefixId + "SongBrowser_FilteredSongPack";
  17. private readonly String CUSTOM_SONGS_DIR = Path.Combine("Beat Saber_Data", "CustomLevels");
  18. private readonly DateTime EPOCH = new DateTime(1970, 1, 1);
  19. // song_browser_settings.xml
  20. private SongBrowserSettings _settings;
  21. // song list management
  22. private double _customSongDirLastWriteTime = 0;
  23. private Dictionary<String, double> _cachedLastWriteTimes;
  24. private Dictionary<string, int> _levelIdToPlayCount;
  25. public BeatmapCharacteristicSO CurrentBeatmapCharacteristicSO;
  26. public static Func<IBeatmapLevelPack, List<IPreviewBeatmapLevel>> CustomFilterHandler;
  27. public static Action<Dictionary<string, CustomPreviewBeatmapLevel>> didFinishProcessingSongs;
  28. public bool SortWasMissingData { get; private set; } = false;
  29. /// <summary>
  30. /// Get the settings the model is using.
  31. /// </summary>
  32. public SongBrowserSettings Settings
  33. {
  34. get
  35. {
  36. return _settings;
  37. }
  38. }
  39. /// <summary>
  40. /// Get the last selected (stored in settings) level id.
  41. /// </summary>
  42. public String LastSelectedLevelId
  43. {
  44. get
  45. {
  46. return _settings.currentLevelId;
  47. }
  48. set
  49. {
  50. _settings.currentLevelId = value;
  51. _settings.Save();
  52. }
  53. }
  54. /// <summary>
  55. /// Constructor.
  56. /// </summary>
  57. public SongBrowserModel()
  58. {
  59. _cachedLastWriteTimes = new Dictionary<String, double>();
  60. _levelIdToPlayCount = new Dictionary<string, int>();
  61. }
  62. /// <summary>
  63. /// Init this model.
  64. /// </summary>
  65. /// <param name="songSelectionMasterView"></param>
  66. /// <param name="songListViewController"></param>
  67. public void Init()
  68. {
  69. _settings = SongBrowserSettings.Load();
  70. Logger.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode);
  71. }
  72. /// <summary>
  73. /// Easy invert of toggling.
  74. /// </summary>
  75. public void ToggleInverting()
  76. {
  77. this.Settings.invertSortResults = !this.Settings.invertSortResults;
  78. }
  79. /// <summary>
  80. /// Get the song cache from the game.
  81. /// </summary>
  82. public void UpdateLevelRecords()
  83. {
  84. Stopwatch timer = new Stopwatch();
  85. timer.Start();
  86. // Calculate some information about the custom song dir
  87. String customSongsPath = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
  88. String revSlashCustomSongPath = customSongsPath.Replace('\\', '/');
  89. double currentCustomSongDirLastWriteTIme = (File.GetLastWriteTimeUtc(customSongsPath) - EPOCH).TotalMilliseconds;
  90. bool customSongDirChanged = false;
  91. if (_customSongDirLastWriteTime != currentCustomSongDirLastWriteTIme)
  92. {
  93. customSongDirChanged = true;
  94. _customSongDirLastWriteTime = currentCustomSongDirLastWriteTIme;
  95. }
  96. if (!Directory.Exists(customSongsPath))
  97. {
  98. Logger.Error("CustomSong directory is missing...");
  99. return;
  100. }
  101. // Map some data for custom songs
  102. Regex r = new Regex(@"(\d+-\d+)", RegexOptions.IgnoreCase);
  103. Stopwatch lastWriteTimer = new Stopwatch();
  104. lastWriteTimer.Start();
  105. foreach (KeyValuePair<string, CustomPreviewBeatmapLevel> level in SongCore.Loader.CustomLevels)
  106. {
  107. // If we already know this levelID, don't both updating it.
  108. // SongLoader should filter duplicates but in case of failure we don't want to crash
  109. if (!_cachedLastWriteTimes.ContainsKey(level.Value.levelID) || customSongDirChanged)
  110. {
  111. double lastWriteTime = GetSongUserDate(level.Value);
  112. _cachedLastWriteTimes[level.Value.levelID] = lastWriteTime;
  113. }
  114. }
  115. lastWriteTimer.Stop();
  116. Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds);
  117. // Update song Infos, directory tree, and sort
  118. this.UpdatePlayCounts();
  119. // Signal complete
  120. if (SongCore.Loader.CustomLevels.Count > 0)
  121. {
  122. didFinishProcessingSongs?.Invoke(SongCore.Loader.CustomLevels);
  123. }
  124. timer.Stop();
  125. Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds);
  126. }
  127. /// <summary>
  128. /// Try to get the date from the cover file, likely the most reliable.
  129. /// Fall back on the folders creation date.
  130. /// </summary>
  131. /// <param name="level"></param>
  132. /// <returns></returns>
  133. private double GetSongUserDate(CustomPreviewBeatmapLevel level)
  134. {
  135. var coverPath = Path.Combine(level.customLevelPath, level.standardLevelInfoSaveData.coverImageFilename);
  136. var lastTime = EPOCH;
  137. if (File.Exists(coverPath))
  138. {
  139. var lastWriteTime = File.GetLastWriteTimeUtc(coverPath);
  140. var lastCreateTime = File.GetCreationTimeUtc(coverPath);
  141. lastTime = lastWriteTime > lastCreateTime ? lastWriteTime : lastCreateTime;
  142. }
  143. else
  144. {
  145. var lastCreateTime = File.GetCreationTimeUtc(level.customLevelPath);
  146. lastTime = lastCreateTime;
  147. }
  148. return (lastTime - EPOCH).TotalMilliseconds;
  149. }
  150. /// <summary>
  151. /// SongLoader doesn't fire event when we delete a song.
  152. /// </summary>
  153. /// <param name="levelPack"></param>
  154. /// <param name="levelId"></param>
  155. public void RemoveSongFromLevelPack(IBeatmapLevelPack levelPack, String levelId)
  156. {
  157. levelPack.beatmapLevelCollection.beatmapLevels.ToList().RemoveAll(x => x.levelID == levelId);
  158. }
  159. /// <summary>
  160. /// Update the gameplay play counts.
  161. /// </summary>
  162. /// <param name="gameplayMode"></param>
  163. private void UpdatePlayCounts()
  164. {
  165. // Reset current playcounts
  166. _levelIdToPlayCount = new Dictionary<string, int>();
  167. // Build a map of levelId to sum of all playcounts and sort.
  168. PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();
  169. foreach (var levelData in playerData.playerData.levelsStatsData)
  170. {
  171. if (!_levelIdToPlayCount.ContainsKey(levelData.levelID))
  172. {
  173. _levelIdToPlayCount.Add(levelData.levelID, 0);
  174. }
  175. _levelIdToPlayCount[levelData.levelID] += levelData.playCount;
  176. }
  177. }
  178. /// <summary>
  179. /// Sort the song list based on the settings.
  180. /// </summary>
  181. public void ProcessSongList(IBeatmapLevelPack selectedLevelPack, LevelCollectionViewController levelCollectionViewController, LevelSelectionNavigationController navController)
  182. {
  183. Logger.Trace("ProcessSongList()");
  184. List<IPreviewBeatmapLevel> unsortedSongs = null;
  185. List<IPreviewBeatmapLevel> filteredSongs = null;
  186. List<IPreviewBeatmapLevel> sortedSongs = null;
  187. // Abort
  188. if (selectedLevelPack == null)
  189. {
  190. Logger.Debug("Cannot process songs yet, no level pack selected...");
  191. return;
  192. }
  193. Logger.Debug("Using songs from level pack: {0}", selectedLevelPack.packID);
  194. unsortedSongs = selectedLevelPack.beatmapLevelCollection.beatmapLevels.ToList();
  195. // filter
  196. Logger.Debug($"Starting filtering songs by {_settings.filterMode}");
  197. Stopwatch stopwatch = Stopwatch.StartNew();
  198. switch (_settings.filterMode)
  199. {
  200. case SongFilterMode.Favorites:
  201. filteredSongs = FilterFavorites(unsortedSongs);
  202. break;
  203. case SongFilterMode.Search:
  204. filteredSongs = FilterSearch(unsortedSongs);
  205. break;
  206. case SongFilterMode.Ranked:
  207. filteredSongs = FilterRanked(unsortedSongs, true, false);
  208. break;
  209. case SongFilterMode.Unranked:
  210. filteredSongs = FilterRanked(unsortedSongs, false, true);
  211. break;
  212. case SongFilterMode.Custom:
  213. Logger.Info("Song filter mode set to custom. Deferring filter behaviour to another mod.");
  214. filteredSongs = CustomFilterHandler != null ? CustomFilterHandler.Invoke(selectedLevelPack) : unsortedSongs;
  215. break;
  216. case SongFilterMode.None:
  217. default:
  218. Logger.Info("No song filter selected...");
  219. filteredSongs = unsortedSongs;
  220. break;
  221. }
  222. stopwatch.Stop();
  223. Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds);
  224. // sort
  225. Logger.Debug("Starting to sort songs...");
  226. stopwatch = Stopwatch.StartNew();
  227. SortWasMissingData = false;
  228. switch (_settings.sortMode)
  229. {
  230. case SongSortMode.Original:
  231. sortedSongs = SortOriginal(filteredSongs);
  232. break;
  233. case SongSortMode.Newest:
  234. sortedSongs = SortNewest(filteredSongs);
  235. break;
  236. case SongSortMode.Author:
  237. sortedSongs = SortAuthor(filteredSongs);
  238. break;
  239. case SongSortMode.UpVotes:
  240. sortedSongs = SortUpVotes(filteredSongs);
  241. break;
  242. case SongSortMode.PlayCount:
  243. sortedSongs = SortBeatSaverPlayCount(filteredSongs);
  244. break;
  245. case SongSortMode.Rating:
  246. sortedSongs = SortBeatSaverRating(filteredSongs);
  247. break;
  248. case SongSortMode.Heat:
  249. sortedSongs = SortBeatSaverHeat(filteredSongs);
  250. break;
  251. case SongSortMode.YourPlayCount:
  252. sortedSongs = SortPlayCount(filteredSongs);
  253. break;
  254. case SongSortMode.PP:
  255. sortedSongs = SortPerformancePoints(filteredSongs);
  256. break;
  257. case SongSortMode.Stars:
  258. sortedSongs = SortStars(filteredSongs);
  259. break;
  260. case SongSortMode.Random:
  261. sortedSongs = SortRandom(filteredSongs);
  262. break;
  263. case SongSortMode.Default:
  264. default:
  265. sortedSongs = SortSongName(filteredSongs);
  266. break;
  267. }
  268. if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random)
  269. {
  270. sortedSongs.Reverse();
  271. }
  272. stopwatch.Stop();
  273. Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
  274. // Asterisk the pack name so it is identifable as filtered.
  275. var packName = selectedLevelPack.packName;
  276. if (!packName.EndsWith("*") && _settings.filterMode != SongFilterMode.None)
  277. {
  278. packName += "*";
  279. }
  280. BeatmapLevelPack levelPack = new BeatmapLevelPack(SongBrowserModel.FilteredSongsPackId, packName, selectedLevelPack.shortPackName, selectedLevelPack.coverImage, new BeatmapLevelCollection(sortedSongs.ToArray()));
  281. GameObject _noDataGO = levelCollectionViewController.GetPrivateField<GameObject>("_noDataInfoGO");
  282. //string _headerText = tableView.GetPrivateField<string>("_headerText");
  283. //Sprite _headerSprite = tableView.GetPrivateField<Sprite>("_headerSprite");
  284. bool _showPlayerStatsInDetailView = navController.GetPrivateField<bool>("_showPlayerStatsInDetailView");
  285. bool _showPracticeButtonInDetailView = navController.GetPrivateField<bool>("_showPracticeButtonInDetailView");
  286. navController.SetData(levelPack, true, _showPlayerStatsInDetailView, _showPracticeButtonInDetailView, _noDataGO);
  287. //_sortedSongs.ForEach(x => Logger.Debug(x.levelID));
  288. }
  289. /// <summary>
  290. /// Filter songs based on playerdata favorites.
  291. /// </summary>
  292. private List<IPreviewBeatmapLevel> FilterFavorites(List<IPreviewBeatmapLevel> levels)
  293. {
  294. Logger.Info("Filtering song list as favorites playlist...");
  295. PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();
  296. return levels.Where(x => playerData.playerData.favoritesLevelIds.Contains(x.levelID)).ToList();
  297. }
  298. /// <summary>
  299. /// Filter for a search query.
  300. /// </summary>
  301. /// <param name="levels"></param>
  302. /// <returns></returns>
  303. private List<IPreviewBeatmapLevel> FilterSearch(List<IPreviewBeatmapLevel> levels)
  304. {
  305. // Make sure we can actually search.
  306. if (this._settings.searchTerms.Count <= 0)
  307. {
  308. Logger.Error("Tried to search for a song with no valid search terms...");
  309. SortSongName(levels);
  310. return levels;
  311. }
  312. string searchTerm = this._settings.searchTerms[0];
  313. if (String.IsNullOrEmpty(searchTerm))
  314. {
  315. Logger.Error("Empty search term entered.");
  316. SortSongName(levels);
  317. return levels;
  318. }
  319. Logger.Info("Filtering song list by search term: {0}", searchTerm);
  320. var terms = searchTerm.Split(' ');
  321. foreach (var term in terms)
  322. {
  323. levels = levels.Intersect(
  324. levels
  325. .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName} {x.levelAuthorName}".ToLower().Contains(term.ToLower()))
  326. .ToList(
  327. )
  328. ).ToList();
  329. }
  330. return levels;
  331. }
  332. /// <summary>
  333. /// Filter songs based on ranked or unranked status.
  334. /// </summary>
  335. /// <param name="levels"></param>
  336. /// <param name="includeRanked"></param>
  337. /// <param name="includeUnranked"></param>
  338. /// <returns></returns>
  339. private List<IPreviewBeatmapLevel> FilterRanked(List<IPreviewBeatmapLevel> levels, bool includeRanked, bool includeUnranked)
  340. {
  341. return levels.Where(x =>
  342. {
  343. var hash = SongBrowserModel.GetSongHash(x.levelID);
  344. double maxPP = 0.0;
  345. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  346. {
  347. maxPP = SongDataCore.Plugin.Songs.Data.Songs[hash].diffs.Max(y => y.pp);
  348. }
  349. if (maxPP > 0f)
  350. {
  351. return includeRanked;
  352. }
  353. else
  354. {
  355. return includeUnranked;
  356. }
  357. }).ToList();
  358. }
  359. /// <summary>
  360. /// Sorting returns original list.
  361. /// </summary>
  362. /// <param name="levels"></param>
  363. /// <returns></returns>
  364. private List<IPreviewBeatmapLevel> SortOriginal(List<IPreviewBeatmapLevel> levels)
  365. {
  366. Logger.Info("Sorting song list as original");
  367. return levels;
  368. }
  369. /// <summary>
  370. /// Sorting by newest (file time, creation+modified).
  371. /// </summary>
  372. /// <param name="levels"></param>
  373. /// <returns></returns>
  374. private List<IPreviewBeatmapLevel> SortNewest(List<IPreviewBeatmapLevel> levels)
  375. {
  376. Logger.Info("Sorting song list as newest.");
  377. return levels
  378. .OrderByDescending(x => _cachedLastWriteTimes.ContainsKey(x.levelID) ? _cachedLastWriteTimes[x.levelID] : int.MinValue)
  379. .ToList();
  380. }
  381. /// <summary>
  382. /// Sorting by the song author.
  383. /// </summary>
  384. /// <param name="levelIds"></param>
  385. /// <returns></returns>
  386. private List<IPreviewBeatmapLevel> SortAuthor(List<IPreviewBeatmapLevel> levelIds)
  387. {
  388. Logger.Info("Sorting song list by author");
  389. return levelIds
  390. .OrderBy(x => x.songAuthorName)
  391. .ThenBy(x => x.songName)
  392. .ToList();
  393. }
  394. /// <summary>
  395. /// Sorting by song users play count.
  396. /// </summary>
  397. /// <param name="levels"></param>
  398. /// <returns></returns>
  399. private List<IPreviewBeatmapLevel> SortPlayCount(List<IPreviewBeatmapLevel> levels)
  400. {
  401. Logger.Info("Sorting song list by playcount");
  402. return levels
  403. .OrderByDescending(x => _levelIdToPlayCount.ContainsKey(x.levelID) ? _levelIdToPlayCount[x.levelID] : 0)
  404. .ThenBy(x => x.songName)
  405. .ToList();
  406. }
  407. /// <summary>
  408. /// Sorting by PP.
  409. /// </summary>
  410. /// <param name="levels"></param>
  411. /// <returns></returns>
  412. private List<IPreviewBeatmapLevel> SortPerformancePoints(List<IPreviewBeatmapLevel> levels)
  413. {
  414. Logger.Info("Sorting song list by performance points...");
  415. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  416. {
  417. SortWasMissingData = true;
  418. return levels;
  419. }
  420. return levels
  421. .OrderByDescending(x =>
  422. {
  423. var hash = SongBrowserModel.GetSongHash(x.levelID);
  424. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  425. {
  426. return SongDataCore.Plugin.Songs.Data.Songs[hash].diffs.Max(y => y.pp);
  427. }
  428. else
  429. {
  430. return 0;
  431. }
  432. })
  433. .ToList();
  434. }
  435. /// <summary>
  436. /// Sorting by star rating.
  437. /// </summary>
  438. /// <param name="levels"></param>
  439. /// <returns></returns>
  440. private List<IPreviewBeatmapLevel> SortStars(List<IPreviewBeatmapLevel> levels)
  441. {
  442. Logger.Info("Sorting song list by star points...");
  443. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  444. {
  445. SortWasMissingData = true;
  446. return levels;
  447. }
  448. return levels
  449. .OrderByDescending(x =>
  450. {
  451. var hash = SongBrowserModel.GetSongHash(x.levelID);
  452. var stars = 0.0;
  453. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  454. {
  455. var diffs = SongDataCore.Plugin.Songs.Data.Songs[hash].diffs;
  456. stars = diffs.Max(y => y.star);
  457. }
  458. //Logger.Debug("Stars={0}", stars);
  459. if (stars != 0)
  460. {
  461. return stars;
  462. }
  463. if (_settings.invertSortResults)
  464. {
  465. return double.MaxValue;
  466. }
  467. else
  468. {
  469. return double.MinValue;
  470. }
  471. })
  472. .ToList();
  473. }
  474. /// <summary>
  475. /// Randomize the sorting.
  476. /// </summary>
  477. /// <param name="levelIds"></param>
  478. /// <returns></returns>
  479. private List<IPreviewBeatmapLevel> SortRandom(List<IPreviewBeatmapLevel> levelIds)
  480. {
  481. Logger.Info("Sorting song list by random (seed={0})...", Settings.randomSongSeed);
  482. System.Random rnd = new System.Random(Settings.randomSongSeed);
  483. return levelIds
  484. .OrderBy(x => x.songName)
  485. .OrderBy(x => rnd.Next())
  486. .ToList();
  487. }
  488. /// <summary>
  489. /// Sorting by the song name.
  490. /// </summary>
  491. /// <param name="levels"></param>
  492. /// <returns></returns>
  493. private List<IPreviewBeatmapLevel> SortSongName(List<IPreviewBeatmapLevel> levels)
  494. {
  495. Logger.Info("Sorting song list as default (songName)");
  496. return levels
  497. .OrderBy(x => x.songName)
  498. .ThenBy(x => x.songAuthorName)
  499. .ToList();
  500. }
  501. /// <summary>
  502. /// Sorting by BeatSaver UpVotes.
  503. /// </summary>
  504. /// <param name="levelIds"></param>
  505. /// <returns></returns>
  506. private List<IPreviewBeatmapLevel> SortUpVotes(List<IPreviewBeatmapLevel> levelIds)
  507. {
  508. Logger.Info("Sorting song list by BeatSaver UpVotes");
  509. // Do not always have data when trying to sort by UpVotes
  510. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  511. {
  512. SortWasMissingData = true;
  513. return levelIds;
  514. }
  515. return levelIds
  516. .OrderByDescending(x => {
  517. var hash = SongBrowserModel.GetSongHash(x.levelID);
  518. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  519. {
  520. return SongDataCore.Plugin.Songs.Data.Songs[hash].upVotes;
  521. }
  522. else
  523. {
  524. return int.MinValue;
  525. }
  526. })
  527. .ToList();
  528. }
  529. /// <summary>
  530. /// Sorting by BeatSaver playcount stat.
  531. /// </summary>
  532. /// <param name="levelIds"></param>
  533. /// <returns></returns>
  534. private List<IPreviewBeatmapLevel> SortBeatSaverPlayCount(List<IPreviewBeatmapLevel> levelIds)
  535. {
  536. Logger.Info("Sorting song list by BeatSaver PlayCount");
  537. return levelIds;
  538. // Do not always have data when trying to sort by UpVotes
  539. /*if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  540. {
  541. SortWasMissingData = true;
  542. return levelIds;
  543. }
  544. return levelIds
  545. .OrderByDescending(x => {
  546. var hash = SongBrowserModel.GetSongHash(x.levelID);
  547. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  548. {
  549. return SongDataCore.Plugin.Songs.Data.Songs[hash].plays;
  550. }
  551. else
  552. {
  553. return int.MinValue;
  554. }
  555. })
  556. .ToList();*/
  557. }
  558. /// <summary>
  559. /// Sorting by BeatSaver rating stat.
  560. /// </summary>
  561. /// <param name="levelIds"></param>
  562. /// <returns></returns>
  563. private List<IPreviewBeatmapLevel> SortBeatSaverRating(List<IPreviewBeatmapLevel> levelIds)
  564. {
  565. Logger.Info("Sorting song list by BeatSaver Rating!");
  566. // Do not always have data when trying to sort by rating
  567. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  568. {
  569. SortWasMissingData = true;
  570. return levelIds;
  571. }
  572. return levelIds
  573. .OrderByDescending(x => {
  574. var hash = SongBrowserModel.GetSongHash(x.levelID);
  575. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  576. {
  577. return SongDataCore.Plugin.Songs.Data.Songs[hash].rating;
  578. }
  579. else
  580. {
  581. return int.MinValue;
  582. }
  583. })
  584. .ToList();
  585. }
  586. /// <summary>
  587. /// Sorting by BeatSaver heat stat.
  588. /// </summary>
  589. /// <param name="levelIds"></param>
  590. /// <returns></returns>
  591. private List<IPreviewBeatmapLevel> SortBeatSaverHeat(List<IPreviewBeatmapLevel> levelIds)
  592. {
  593. Logger.Info("Sorting song list by BeatSaver Heat!");
  594. // Do not always have data when trying to sort by heat
  595. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  596. {
  597. SortWasMissingData = true;
  598. return levelIds;
  599. }
  600. return levelIds
  601. .OrderByDescending(x => {
  602. var hash = SongBrowserModel.GetSongHash(x.levelID);
  603. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  604. {
  605. return SongDataCore.Plugin.Songs.Data.Songs[hash].heat;
  606. }
  607. else
  608. {
  609. return int.MinValue;
  610. }
  611. })
  612. .ToList();
  613. }
  614. #region Song helpers
  615. /// <summary>
  616. /// Get the song hash from a levelID
  617. /// </summary>
  618. /// <param name="levelId"></param>
  619. /// <returns></returns>
  620. public static string GetSongHash(string levelId)
  621. {
  622. var split = levelId.Split('_');
  623. return split.Length > 2 ? split[2] : levelId;
  624. }
  625. #endregion
  626. }
  627. }