SongBrowserModel.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. using SongBrowser.DataAccess;
  2. using SongBrowser.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 = SongBrowser.Logging.Logger;
  12. namespace SongBrowser
  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. Stopwatch timer = new Stopwatch();
  196. timer.Start();
  197. // Calculate some information about the custom song dir
  198. String customSongsPath = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
  199. String revSlashCustomSongPath = customSongsPath.Replace('\\', '/');
  200. double currentCustomSongDirLastWriteTIme = (File.GetLastWriteTimeUtc(customSongsPath) - EPOCH).TotalMilliseconds;
  201. bool customSongDirChanged = false;
  202. if (_customSongDirLastWriteTime != currentCustomSongDirLastWriteTIme)
  203. {
  204. customSongDirChanged = true;
  205. _customSongDirLastWriteTime = currentCustomSongDirLastWriteTIme;
  206. }
  207. if (!Directory.Exists(customSongsPath))
  208. {
  209. Logger.Error("CustomSong directory is missing...");
  210. return;
  211. }
  212. IEnumerable<string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories);
  213. // Get LastWriteTimes
  214. Stopwatch lastWriteTimer = new Stopwatch();
  215. lastWriteTimer.Start();
  216. foreach (var level in SongLoader.CustomLevels)
  217. {
  218. // If we already know this levelID, don't both updating it.
  219. // SongLoader should filter duplicates but in case of failure we don't want to crash
  220. if (!_cachedLastWriteTimes.ContainsKey(level.levelID) || customSongDirChanged)
  221. {
  222. // Always use the newest date.
  223. var lastWriteTime = File.GetLastWriteTimeUtc(level.customSongInfo.path);
  224. var lastCreateTime = File.GetCreationTimeUtc(level.customSongInfo.path);
  225. var lastTime = lastWriteTime > lastCreateTime ? lastWriteTime : lastCreateTime;
  226. _cachedLastWriteTimes[level.levelID] = (lastTime - EPOCH).TotalMilliseconds;
  227. }
  228. if (!_levelIdToCustomLevel.ContainsKey(level.levelID))
  229. {
  230. _levelIdToCustomLevel.Add(level.levelID, level);
  231. }
  232. if (!_levelIdToSongVersion.ContainsKey(level.levelID))
  233. {
  234. DirectoryInfo info = new DirectoryInfo(level.customSongInfo.path);
  235. string currentDirectoryName = info.Name;
  236. String version = level.customSongInfo.path.Replace(revSlashCustomSongPath, "").Replace(currentDirectoryName, "").Replace("/", "");
  237. if (!String.IsNullOrEmpty(version))
  238. {
  239. _levelIdToSongVersion.Add(level.levelID, version);
  240. if (!_keyToSong.ContainsKey(version))
  241. {
  242. _keyToSong.Add(version, level);
  243. }
  244. }
  245. }
  246. }
  247. lastWriteTimer.Stop();
  248. Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds);
  249. // Update song Infos, directory tree, and sort
  250. this.UpdateScoreSaberDataMapping();
  251. this.UpdatePlayCounts();
  252. // Check if we need to upgrade settings file favorites
  253. try
  254. {
  255. this.Settings.ConvertFavoritesToPlaylist(_levelIdToCustomLevel, _levelIdToSongVersion);
  256. }
  257. catch (Exception e)
  258. {
  259. Logger.Exception("FAILED TO CONVERT FAVORITES TO PLAYLIST!", e);
  260. }
  261. // load the current editing playlist or make one
  262. if (CurrentEditingPlaylist == null && !String.IsNullOrEmpty(this.Settings.currentEditingPlaylistFile))
  263. {
  264. Logger.Debug("Loading playlist for editing: {0}", this.Settings.currentEditingPlaylistFile);
  265. CurrentEditingPlaylist = Playlist.LoadPlaylist(this.Settings.currentEditingPlaylistFile);
  266. PlaylistsCollection.MatchSongsForPlaylist(CurrentEditingPlaylist);
  267. }
  268. if (CurrentEditingPlaylist == null)
  269. {
  270. Logger.Debug("Current editing playlist does not exit, create...");
  271. CurrentEditingPlaylist = new Playlist
  272. {
  273. playlistTitle = "Song Browser Favorites",
  274. playlistAuthor = "SongBrowser",
  275. fileLoc = this.Settings.currentEditingPlaylistFile,
  276. image = Base64Sprites.PlaylistIconB64,
  277. songs = new List<PlaylistSong>(),
  278. };
  279. }
  280. CurrentEditingPlaylistLevelIds = new HashSet<string>();
  281. foreach (PlaylistSong ps in CurrentEditingPlaylist.songs)
  282. {
  283. // Sometimes we cannot match a song
  284. if (ps.level == null)
  285. {
  286. continue;
  287. }
  288. CurrentEditingPlaylistLevelIds.Add(ps.level.levelID);
  289. }
  290. // Actually sort and filter
  291. //this.ProcessSongList();
  292. // Signal complete
  293. if (SongLoader.CustomLevels.Count > 0)
  294. {
  295. didFinishProcessingSongs?.Invoke(SongLoader.CustomLevels);
  296. }
  297. timer.Stop();
  298. Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds);
  299. }
  300. /// <summary>
  301. /// Get a copy of the unfiltered, unsorted list of songs from level packs.
  302. /// </summary>
  303. public void UpdateLevelPackOriginalLists(bool refreshOnlyUnknown)
  304. {
  305. BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
  306. foreach (BeatmapLevelPackSO levelPack in levelPacks)
  307. {
  308. Logger.Debug("Attempting to get song list from levelPack: {0}...", levelPack);
  309. var beatmapLevelPack = levelPack as BeatmapLevelPackSO;
  310. // TODO - need to rethink interface here, not all level packs can be cast this high, some sort functions need it.
  311. // - this helps prevent DLC from breaking everything
  312. if (beatmapLevelPack == null)
  313. {
  314. continue;
  315. }
  316. if (refreshOnlyUnknown && _levelPackToSongs.ContainsKey(levelPack.packName))
  317. {
  318. Logger.Debug("Already know about {0}, not refreshing...", levelPack.packName);
  319. continue;
  320. }
  321. _levelPackToSongs[levelPack.packName] = (beatmapLevelPack.beatmapLevelCollection as BeatmapLevelCollectionSO).GetPrivateField<BeatmapLevelSO[]>("_beatmapLevels").ToList();
  322. Logger.Debug("Got {0} songs from level collections...", _levelPackToSongs[levelPack.packName].Count);
  323. //_levelPackToSongs[levelPack.packName].ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID));
  324. }
  325. }
  326. /// <summary>
  327. /// SongLoader doesn't fire event when we delete a song.
  328. /// </summary>
  329. /// <param name="levelPack"></param>
  330. /// <param name="levelId"></param>
  331. public void RemoveSongFromLevelPack(IBeatmapLevelPack levelPack, String levelId)
  332. {
  333. if (!_levelPackToSongs.ContainsKey(levelPack.packName))
  334. {
  335. Logger.Debug("Trying to remove song from level pack [{0}] but we do not have any information on it...", levelPack.packName);
  336. return;
  337. }
  338. _levelPackToSongs[levelPack.packName].RemoveAll(x => x.levelID == levelId);
  339. }
  340. /// <summary>
  341. /// Update the gameplay play counts.
  342. /// </summary>
  343. /// <param name="gameplayMode"></param>
  344. private void UpdatePlayCounts()
  345. {
  346. // Build a map of levelId to sum of all playcounts and sort.
  347. PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();
  348. foreach (KeyValuePair<string, List<BeatmapLevelSO>> entry in _levelPackToSongs)
  349. {
  350. foreach (var level in entry.Value)
  351. {
  352. if (!_levelIdToPlayCount.ContainsKey(level.levelID))
  353. {
  354. // Skip folders
  355. int playCountSum = 0;
  356. foreach (IDifficultyBeatmapSet set in level.difficultyBeatmapSets)
  357. {
  358. foreach (IDifficultyBeatmap beatmap in set.difficultyBeatmaps)
  359. {
  360. PlayerLevelStatsData stats = playerData.currentLocalPlayer.GetPlayerLevelStatsData(level.levelID, beatmap.difficulty, set.beatmapCharacteristic);
  361. playCountSum += stats.playCount;
  362. }
  363. }
  364. _levelIdToPlayCount.Add(level.levelID, playCountSum);
  365. }
  366. }
  367. }
  368. }
  369. /// <summary>
  370. /// Parse the current pp data file.
  371. /// Public so controllers can decide when to update it.
  372. /// </summary>
  373. public void UpdateScoreSaberDataMapping()
  374. {
  375. Logger.Trace("UpdateScoreSaberDataMapping()");
  376. ScoreSaberDataFile scoreSaberDataFile = ScoreSaberDatabaseDownloader.ScoreSaberDataFile;
  377. // bail
  378. if (scoreSaberDataFile == null)
  379. {
  380. Logger.Warning("Score saber data is not ready yet...");
  381. return;
  382. }
  383. foreach (var level in SongLoader.CustomLevels)
  384. {
  385. // Skip
  386. if (_levelIdToScoreSaberData.ContainsKey(level.levelID))
  387. {
  388. continue;
  389. }
  390. ScoreSaberData scoreSaberData = null;
  391. // try to version match first
  392. if (_levelIdToSongVersion.ContainsKey(level.levelID))
  393. {
  394. String version = _levelIdToSongVersion[level.levelID];
  395. if (scoreSaberDataFile.SongVersionToScoreSaberData.ContainsKey(version))
  396. {
  397. scoreSaberData = scoreSaberDataFile.SongVersionToScoreSaberData[version];
  398. }
  399. }
  400. if (scoreSaberData != null)
  401. {
  402. //Logger.Debug("{0} = {1}pp", level.songName, pp);
  403. _levelIdToScoreSaberData.Add(level.levelID, scoreSaberData);
  404. }
  405. }
  406. }
  407. /// <summary>
  408. /// Add Song to Editing Playlist
  409. /// </summary>
  410. /// <param name="songInfo"></param>
  411. public void AddSongToEditingPlaylist(IBeatmapLevel songInfo)
  412. {
  413. if (this.CurrentEditingPlaylist == null)
  414. {
  415. return;
  416. }
  417. this.CurrentEditingPlaylist.songs.Add(new PlaylistSong()
  418. {
  419. songName = songInfo.songName,
  420. levelId = songInfo.levelID,
  421. key = _levelIdToSongVersion.ContainsKey(songInfo.levelID) ? _levelIdToSongVersion[songInfo.levelID] : songInfo.levelID,
  422. });
  423. this.CurrentEditingPlaylistLevelIds.Add(songInfo.levelID);
  424. this.CurrentEditingPlaylist.SavePlaylist();
  425. }
  426. /// <summary>
  427. /// Remove Song from editing playlist
  428. /// </summary>
  429. /// <param name="levelId"></param>
  430. public void RemoveSongFromEditingPlaylist(IBeatmapLevel songInfo)
  431. {
  432. if (this.CurrentEditingPlaylist == null)
  433. {
  434. return;
  435. }
  436. this.CurrentEditingPlaylist.songs.RemoveAll(x => x.level != null && x.level.levelID == songInfo.levelID);
  437. this.CurrentEditingPlaylistLevelIds.RemoveWhere(x => x == songInfo.levelID);
  438. this.CurrentEditingPlaylist.SavePlaylist();
  439. }
  440. /// <summary>
  441. /// Resets all the level packs back to their original values.
  442. /// </summary>
  443. /// <param name="pack"></param>
  444. private void ResetLevelPacks()
  445. {
  446. Logger.Debug("Setting level packs back to their original values!");
  447. BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
  448. foreach (BeatmapLevelPackSO levelPack in levelPacks)
  449. {
  450. if (levelPack.packName == null || !_levelPackToSongs.ContainsKey(levelPack.packName))
  451. {
  452. Logger.Debug("We know nothing about pack: {0}", levelPack.packName);
  453. continue;
  454. }
  455. var levels = _levelPackToSongs[levelPack.packName].ToArray();
  456. ReflectionUtil.SetPrivateField(levelPack.beatmapLevelCollection, "_beatmapLevels", levels);
  457. }
  458. }
  459. /// <summary>
  460. /// Set current level pack, reset all packs just in case.
  461. /// </summary>
  462. /// <param name="pack"></param>
  463. public void SetCurrentLevelPack(IBeatmapLevelPack pack)
  464. {
  465. Logger.Debug("Setting current level pack [{0}]: {1}", pack.packID, pack.packName);
  466. this.ResetLevelPacks();
  467. this._currentLevelPack = pack;
  468. var beatmapLevelPack = pack as BeatmapLevelPackSO;
  469. if (beatmapLevelPack == null)
  470. {
  471. Logger.Debug("DLC Detected... Disabling SongBrowser...");
  472. _isPreviewLevelPack = true;
  473. }
  474. else
  475. {
  476. Logger.Debug("Owned level pack... Enabling SongBrowser...");
  477. _isPreviewLevelPack = false;
  478. }
  479. this.Settings.currentLevelPackId = pack.packID;
  480. this.Settings.Save();
  481. }
  482. /// <summary>
  483. /// Overwrite the current level pack.
  484. /// </summary>
  485. private void OverwriteCurrentLevelPack()
  486. {
  487. Logger.Debug("Overwriting levelPack [{0}] beatmapLevelCollection", this._currentLevelPack);
  488. IBeatmapLevelPack levelPack = this._currentLevelPack;
  489. var levels = _sortedSongs.ToArray();
  490. ReflectionUtil.SetPrivateField(levelPack.beatmapLevelCollection, "_beatmapLevels", levels);
  491. }
  492. /// <summary>
  493. /// Sort the song list based on the settings.
  494. /// </summary>
  495. public void ProcessSongList()
  496. {
  497. Logger.Trace("ProcessSongList()");
  498. // This has come in handy many times for debugging issues with Newest.
  499. /*foreach (BeatmapLevelSO level in _originalSongs)
  500. {
  501. if (_levelIdToCustomLevel.ContainsKey(level.levelID))
  502. {
  503. Logger.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID);
  504. }
  505. else
  506. {
  507. Logger.Debug("Missing KEY: {0}", level.levelID);
  508. }
  509. }*/
  510. // TODO - remove as part of unifying song list interface
  511. if (_isPreviewLevelPack)
  512. {
  513. return;
  514. }
  515. if (_levelPackToSongs.Count == 0)
  516. {
  517. Logger.Debug("Cannot process songs yet, level packs have not been processed...");
  518. return;
  519. }
  520. if (this._currentLevelPack == null || !this._levelPackToSongs.ContainsKey(this._currentLevelPack.packName))
  521. {
  522. Logger.Debug("Cannot process songs yet, no level pack selected...");
  523. return;
  524. }
  525. // Playlist filter will load the original songs.
  526. List<BeatmapLevelSO> unsortedSongs = null;
  527. List<BeatmapLevelSO> filteredSongs = null;
  528. if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null)
  529. {
  530. unsortedSongs = null;
  531. }
  532. else
  533. {
  534. Logger.Debug("Using songs from level pack: {0}", this._currentLevelPack.packName);
  535. unsortedSongs = new List<BeatmapLevelSO>(_levelPackToSongs[this._currentLevelPack.packName]);
  536. }
  537. // filter
  538. Logger.Debug("Starting filtering songs...");
  539. Stopwatch stopwatch = Stopwatch.StartNew();
  540. switch (_settings.filterMode)
  541. {
  542. case SongFilterMode.Favorites:
  543. filteredSongs = FilterFavorites();
  544. break;
  545. case SongFilterMode.Search:
  546. filteredSongs = FilterSearch(unsortedSongs);
  547. break;
  548. case SongFilterMode.Playlist:
  549. filteredSongs = FilterPlaylist();
  550. break;
  551. case SongFilterMode.None:
  552. default:
  553. Logger.Info("No song filter selected...");
  554. filteredSongs = unsortedSongs;
  555. break;
  556. }
  557. stopwatch.Stop();
  558. Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds);
  559. // sort
  560. Logger.Debug("Starting to sort songs...");
  561. stopwatch = Stopwatch.StartNew();
  562. switch (_settings.sortMode)
  563. {
  564. case SongSortMode.Original:
  565. SortOriginal(filteredSongs);
  566. break;
  567. case SongSortMode.Newest:
  568. SortNewest(filteredSongs);
  569. break;
  570. case SongSortMode.Author:
  571. SortAuthor(filteredSongs);
  572. break;
  573. case SongSortMode.PlayCount:
  574. SortPlayCount(filteredSongs);
  575. break;
  576. case SongSortMode.PP:
  577. SortPerformancePoints(filteredSongs);
  578. break;
  579. case SongSortMode.Difficulty:
  580. SortDifficulty(filteredSongs);
  581. break;
  582. case SongSortMode.Random:
  583. SortRandom(filteredSongs);
  584. break;
  585. case SongSortMode.Default:
  586. default:
  587. SortSongName(filteredSongs);
  588. break;
  589. }
  590. if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random)
  591. {
  592. _sortedSongs.Reverse();
  593. }
  594. stopwatch.Stop();
  595. Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
  596. this.OverwriteCurrentLevelPack();
  597. //_sortedSongs.ForEach(x => Logger.Debug(x.levelID));
  598. }
  599. /// <summary>
  600. /// For now the editing playlist will be considered the favorites playlist.
  601. /// Users can edit the settings file themselves.
  602. /// </summary>
  603. private List<BeatmapLevelSO> FilterFavorites()
  604. {
  605. Logger.Info("Filtering song list as favorites playlist...");
  606. if (this.CurrentEditingPlaylist != null)
  607. {
  608. this.CurrentPlaylist = this.CurrentEditingPlaylist;
  609. }
  610. return this.FilterPlaylist();
  611. }
  612. private List<BeatmapLevelSO> FilterSearch(List<BeatmapLevelSO> levels)
  613. {
  614. // Make sure we can actually search.
  615. if (this._settings.searchTerms.Count <= 0)
  616. {
  617. Logger.Error("Tried to search for a song with no valid search terms...");
  618. SortSongName(levels);
  619. return levels;
  620. }
  621. string searchTerm = this._settings.searchTerms[0];
  622. if (String.IsNullOrEmpty(searchTerm))
  623. {
  624. Logger.Error("Empty search term entered.");
  625. SortSongName(levels);
  626. return levels;
  627. }
  628. Logger.Info("Filtering song list by search term: {0}", searchTerm);
  629. var terms = searchTerm.Split(' ');
  630. foreach (var term in terms)
  631. {
  632. levels = levels.Intersect(
  633. levels
  634. .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(term.ToLower()))
  635. .ToList(
  636. )
  637. ).ToList();
  638. }
  639. return levels;
  640. }
  641. private List<BeatmapLevelSO> FilterPlaylist()
  642. {
  643. // bail if no playlist, usually means the settings stored one the user then moved.
  644. if (this.CurrentPlaylist == null)
  645. {
  646. Logger.Error("Trying to load a null playlist...");
  647. this.Settings.filterMode = SongFilterMode.None;
  648. return null;
  649. }
  650. // Get song keys
  651. PlaylistsCollection.MatchSongsForPlaylist(this.CurrentPlaylist, true);
  652. Logger.Debug("Filtering songs for playlist: {0}", this.CurrentPlaylist.playlistTitle);
  653. Dictionary<String, BeatmapLevelSO> levelDict = new Dictionary<string, BeatmapLevelSO>();
  654. foreach (KeyValuePair<string, List<BeatmapLevelSO>> entry in _levelPackToSongs)
  655. {
  656. foreach (BeatmapLevelSO level in entry.Value)
  657. {
  658. if (!levelDict.ContainsKey(level.levelID))
  659. {
  660. levelDict.Add(level.levelID, level);
  661. }
  662. }
  663. }
  664. List<BeatmapLevelSO> songList = new List<BeatmapLevelSO>();
  665. foreach (PlaylistSong ps in this.CurrentPlaylist.songs)
  666. {
  667. if (ps.level != null)
  668. {
  669. songList.Add(levelDict[ps.level.levelID]);
  670. }
  671. else
  672. {
  673. Logger.Debug("Could not find song in playlist: {0}", ps.songName);
  674. }
  675. }
  676. Logger.Debug("Playlist filtered song count: {0}", songList.Count);
  677. return songList;
  678. }
  679. private void SortOriginal(List<BeatmapLevelSO> levels)
  680. {
  681. Logger.Info("Sorting song list as original");
  682. _sortedSongs = levels;
  683. }
  684. private void SortNewest(List<BeatmapLevelSO> levels)
  685. {
  686. Logger.Info("Sorting song list as newest.");
  687. _sortedSongs = levels
  688. .OrderBy(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
  689. .ThenByDescending(x => !_levelIdToCustomLevel.ContainsKey(x.levelID) ? (_weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0) : _cachedLastWriteTimes[x.levelID])
  690. .ToList();
  691. }
  692. private void SortAuthor(List<BeatmapLevelSO> levels)
  693. {
  694. Logger.Info("Sorting song list by author");
  695. _sortedSongs = levels
  696. .OrderBy(x => x.songAuthorName)
  697. .ThenBy(x => x.songName)
  698. .ToList();
  699. }
  700. private void SortPlayCount(List<BeatmapLevelSO> levels)
  701. {
  702. Logger.Info("Sorting song list by playcount");
  703. _sortedSongs = levels
  704. .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
  705. .ThenBy(x => x.songName)
  706. .ToList();
  707. }
  708. private void SortPerformancePoints(List<BeatmapLevelSO> levels)
  709. {
  710. Logger.Info("Sorting song list by performance points...");
  711. _sortedSongs = levels
  712. .OrderByDescending(x => _levelIdToScoreSaberData.ContainsKey(x.levelID) ? _levelIdToScoreSaberData[x.levelID].maxPp : 0)
  713. .ToList();
  714. }
  715. private void SortDifficulty(List<BeatmapLevelSO> levels)
  716. {
  717. Logger.Info("Sorting song list by difficulty...");
  718. IEnumerable<BeatmapDifficulty> difficultyIterator = Enum.GetValues(typeof(BeatmapDifficulty)).Cast<BeatmapDifficulty>();
  719. Dictionary<string, int> levelIdToDifficultyValue = new Dictionary<string, int>();
  720. foreach (var level in levels)
  721. {
  722. if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
  723. {
  724. int difficultyValue = 0;
  725. // Get the beatmap difficulties
  726. var difficulties = level.difficultyBeatmapSets
  727. .Where(x => x.beatmapCharacteristic == this.CurrentBeatmapCharacteristicSO)
  728. .SelectMany(x => x.difficultyBeatmaps);
  729. foreach (IDifficultyBeatmap difficultyBeatmap in difficulties)
  730. {
  731. difficultyValue += _difficultyWeights[difficultyBeatmap.difficulty];
  732. }
  733. levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
  734. }
  735. }
  736. _sortedSongs = levels
  737. .OrderBy(x => levelIdToDifficultyValue[x.levelID])
  738. .ThenBy(x => x.songName)
  739. .ToList();
  740. _sortedSongs = levels;
  741. }
  742. private void SortRandom(List<BeatmapLevelSO> levels)
  743. {
  744. Logger.Info("Sorting song list by random (seed={0})...", this.Settings.randomSongSeed);
  745. System.Random rnd = new System.Random(this.Settings.randomSongSeed);
  746. _sortedSongs = levels
  747. .OrderBy(x => rnd.Next())
  748. .ToList();
  749. }
  750. private void SortSongName(List<BeatmapLevelSO> levels)
  751. {
  752. Logger.Info("Sorting song list as default (songName)");
  753. _sortedSongs = levels
  754. .OrderBy(x => x.songName)
  755. .ThenBy(x => x.songAuthorName)
  756. .ToList();
  757. }
  758. }
  759. }