/// <summary> /// Exports the currently loaded replay to a file. /// </summary> public void ExportReplay() { if (IsFetchingOnlineReplay) { return; } Replay replay; try { switch (ResultsType) { case ResultScreenType.Gameplay: case ResultScreenType.Replay: replay = Replay; break; case ResultScreenType.Score: if (Score.IsOnline) { NotificationManager.Show(NotificationLevel.Warning, "You must watch the online replay before exporting it."); return; } replay = new Replay($"{ConfigManager.DataDirectory.Value}/r/{Score.Id}.qr"); Replay = replay; break; default: throw new ArgumentOutOfRangeException(); } } catch (Exception e) { Logger.Error(e, LogType.Runtime); return; } // Run a check for if the replay has data if (!replay.HasData) { NotificationManager.Show(NotificationLevel.Error, "This replay has no data!"); return; } // Don't allow autoplay replays to be exported if (replay.Mods.HasFlag(ModIdentifier.Autoplay)) { NotificationManager.Show(NotificationLevel.Error, "You cannot export replays that have the Autoplay mod"); return; } // Make sure the user isn't spamming the export button and proceed to export if (GameBase.Game.TimeRunning - LastReplayExportTime >= 5000 || LastReplayExportTime == 0) { NotificationManager.Show(NotificationLevel.Info, "One moment. The replay is getting exported."); LastReplayExportTime = GameBase.Game.TimeRunning; ThreadScheduler.Run(() => { var path = $@"{ConfigManager.ReplayDirectory.Value}/{Replay.PlayerName} - {StringHelper.FileNameSafeString(SongTitle)} - {DateTime.Now:yyyyddMMhhmmss}{GameBase.Game.TimeRunning}.qr"; Replay.Write(path); // Open containing folder Utils.NativeUtils.HighlightInFileManager(path); NotificationManager.Show(NotificationLevel.Success, "The replay has been successfully exported!"); }); } else { NotificationManager.Show(NotificationLevel.Error, "Please wait a few moments before exporting again!"); return; } }
/// <summary> /// Exits the screen to schedule loading the map and ultimately the gameplay screen /// </summary> public void ExitToGameplay() { IsExitingToGameplay = true; if (OnlineManager.CurrentGame != null) { var map = MapManager.Selected.Value; var diff = map.DifficultyFromMods(ModManager.Mods); // Prevent host from picking a map not within difficulty range if (diff < OnlineManager.CurrentGame.MinimumDifficultyRating || diff > OnlineManager.CurrentGame.MaximumDifficultyRating) { NotificationManager.Show(NotificationLevel.Error, $"Difficulty rating must be between " + $"{OnlineManager.CurrentGame.MinimumDifficultyRating} and {OnlineManager.CurrentGame.MaximumDifficultyRating} " + $"for this multiplayer match!"); return; } // Pevent host from picking a map not in max song length range if (map.SongLength * ModHelper.GetRateFromMods(ModManager.Mods) / 1000 > OnlineManager.CurrentGame.MaximumSongLength) { NotificationManager.Show(NotificationLevel.Error, $"The maximum length allowed for this multiplayer match is: " + $"{OnlineManager.CurrentGame.MaximumSongLength} seconds"); return; } // Prevent disallowed game modes from being selected if (!OnlineManager.CurrentGame.AllowedGameModes.Contains((byte)map.Mode)) { NotificationManager.Show(NotificationLevel.Error, "You cannot pick maps of this game mode in this multiplayer match!"); return; } // Prevent maps not in range of the minimum and maximum LN% if (map.LNPercentage < OnlineManager.CurrentGame.MinimumLongNotePercentage || map.LNPercentage > OnlineManager.CurrentGame.MaximumLongNotePercentage) { NotificationManager.Show(NotificationLevel.Error, $"You cannot select this map. The long note percentage must be between " + $"{OnlineManager.CurrentGame.MinimumLongNotePercentage}%-{OnlineManager.CurrentGame.MaximumLongNotePercentage}% " + $"for this multiplayer match."); return; } // Start the fade out early to make it look like the screen is loading Transitioner.FadeIn(); ThreadScheduler.Run(() => { OnlineManager.Client.ChangeMultiplayerGameMap(map.Md5Checksum, map.MapId, map.MapSetId, map.ToString(), (byte)map.Mode, map.DifficultyFromMods(ModManager.Mods), map.GetDifficultyRatings(), map.GetJudgementCount(), MapManager.Selected.Value.GetAlternativeMd5()); OnlineManager.Client.SetGameCurrentlySelectingMap(false); RemoveTopScreen(MultiplayerScreen); }); return; } Exit(() => { var game = GameBase.Game as QuaverGame; var cursor = game.GlobalUserInterface.Cursor; cursor.Animations.Add(new Animation(AnimationProperty.Alpha, Easing.Linear, cursor.Alpha, 0, 200)); if (AudioEngine.Track != null) { lock (AudioEngine.Track) AudioEngine.Track?.Fade(10, 500); } return(new MapLoadingScreen(new List <Score>())); }, 100); }
/// <summary> /// Creates the navbar at the bottom of the screen /// </summary> private void CreateBottomNavbar() => BottomNavbar = new Navbar(new List <NavbarItem>() { // Mods new NavbarItem("Modifiers", false, (o, e) => DialogManager.Show(new ModifiersDialog()), true, false, true), // Edit new NavbarItem("Edit", false, (o, e) => { NotificationManager.Show(NotificationLevel.Warning, "Not implemented yet. Check back soon!"); }, true, false, true), // Export Mapset new NavbarItem("Export", false, (o, e) => { if (Math.Abs(GameBase.Game.TimeRunning - LastExportTime) < 2000) { NotificationManager.Show(NotificationLevel.Error, "Slow down! You can only export a set every 2 seconds."); return; } LastExportTime = GameBase.Game.TimeRunning; ThreadScheduler.Run(() => { NotificationManager.Show(NotificationLevel.Info, "Exporting mapset to file..."); MapManager.Selected.Value.Mapset.ExportToZip(); NotificationManager.Show(NotificationLevel.Success, "Successfully exported mapset!"); }); }, true, false, true), // Select a random map // new NavbarItem("Random", false, (o, e) => // { // var screen = Screen as SelectScreen; // screen.SelectRandomMap(); // }, true, false, true) }, new List <NavbarItem>() { // Play new NavbarItem("Play", false, (o, e) => { switch (ActiveContainer) { case SelectContainerStatus.Mapsets: SwitchToContainer(SelectContainerStatus.Difficulty); break; case SelectContainerStatus.Difficulty: var screen = Screen as SelectScreen; screen?.ExitToGameplay(); break; default: throw new ArgumentOutOfRangeException(); } }, true, false, true), // Game Options new NavbarItem("Options", false, (o, e) => DialogManager.Show(new SettingsDialog()), true, false, true) }, true) { Parent = Container, Alignment = Alignment.TopLeft, };
/// <summary> /// </summary> /// <exception cref="NotImplementedException"></exception> private void CreateMenuFooter() { var screen = (SelectScreen)Screen; var leftButtons = new List <ButtonText>() { new ButtonText(FontsBitmap.GothamRegular, "Back", 14, (sender, args) => screen.ExitToMenu()), new ButtonText(FontsBitmap.GothamRegular, "Options", 14, (sender, args) => DialogManager.Show(new SettingsDialog())), new ButtonText(FontsBitmap.GothamRegular, "Chat", 14, (sender, args) => { if (OnlineManager.Status.Value != ConnectionStatus.Connected) { NotificationManager.Show(NotificationLevel.Error, "You must be logged in to use the chat!"); return; } ChatManager.ToggleChatOverlay(true); }), new ButtonText(FontsBitmap.GothamRegular, "Download Maps", 14, (sender, args) => { if (OnlineManager.Status.Value != ConnectionStatus.Connected) { NotificationManager.Show(NotificationLevel.Error, "You must be online to download maps!"); return; } if (OnlineManager.CurrentGame != null) { NotificationManager.Show(NotificationLevel.Error, "You cannot download maps while in multiplayer!"); return; } screen.Exit(() => new DownloadScreen()); }), new ButtonText(FontsBitmap.GothamRegular, "Profile", 14, (sender, args) => BrowserHelper.OpenURL($"https://quavergame.com/profile/{ConfigManager.Username.Value}?mode={(int) ConfigManager.SelectedGameMode.Value}")), }; var rightButtons = new List <ButtonText>() { new ButtonText(FontsBitmap.GothamRegular, "Modifiers", 14, (sender, args) => DialogManager.Show(new ModifiersDialog())), new ButtonText(FontsBitmap.GothamRegular, "Export", 14, (sender, args) => { if (Math.Abs(GameBase.Game.TimeRunning - LastExportTime) < 2000) { NotificationManager.Show(NotificationLevel.Error, "Slow down! You can only export a set every 2 seconds."); return; } LastExportTime = GameBase.Game.TimeRunning; ThreadScheduler.Run(() => { NotificationManager.Show(NotificationLevel.Info, "Exporting mapset to file..."); MapManager.Selected.Value.Mapset.ExportToZip(); NotificationManager.Show(NotificationLevel.Success, "Successfully exported mapset!"); }); }), new ButtonText(FontsBitmap.GothamRegular, "Delete", 14, ((sender, args) => { if (MapManager.Selected.Value == null) { return; } screen.DeleteSelected(); })) }; if (OnlineManager.CurrentGame == null) { rightButtons.Add(new ButtonText(FontsBitmap.GothamRegular, "Edit", 14, (sender, args) => screen.ExitToEditor())); } rightButtons.Add(new ButtonText(FontsBitmap.GothamRegular, "Random", 14, (sender, args) => screen.SelectRandomMap())); Footer = new MenuFooter(leftButtons, rightButtons, Colors.MainAccent) { Parent = Container, Alignment = Alignment.BotLeft }; }
private void Close() { ThreadScheduler.Run(JudgementWindowsDatabaseCache.UpdateAll); DialogManager.Dismiss(this); }
/// <summary> /// Uploads the mapset to the server /// </summary> private void UploadMapset() => ThreadScheduler.Run(() => { try { Logger.Important($"Starting to upload mapset...", LogType.Network); var path = MapManager.Selected.Value.Mapset.ExportToZip(false); Response = OnlineManager.Client.UploadMapset(path); Logger.Important($"Uploaded mapset with response: {Response}", LogType.Network); File.Delete(path); // ReSharper disable once SwitchStatementMissingSomeCases switch (Response.Code) { case MapsetSubmissionStatusCode.SuccessUpdated: case MapsetSubmissionStatusCode.SuccessUploaded: // Get all files in the directory and delete them, so we can get the updated ones foreach (var f in Directory.GetFiles($"{ConfigManager.SongDirectory.Value}/{MapManager.Selected.Value.Directory}", "*.qua")) { File.Delete(f); } using (var conn = new SQLiteConnection(MapDatabaseCache.DatabasePath)) MapManager.Selected.Value.Mapset.Maps.ForEach(x => conn.Delete(x)); foreach (var map in Response.Maps) { if (map == null) { continue; } var filePath = $"{ConfigManager.SongDirectory.Value}/{MapManager.Selected.Value.Directory}/{map.Id}.qua"; Logger.Important($"Commencing download for map: {map.Id}", LogType.Runtime); try { OnlineManager.Client.DownloadMap(filePath, map.Id); Logger.Important($"Successfully downloaded map: {map.Id}", LogType.Network); } catch (Exception) { continue; } MapDatabaseCache.MapsToUpdate.Add(Map.FromQua(Qua.Parse(filePath), filePath)); Thread.Sleep(1000); } DividerLine.Tint = Color.LimeGreen; TopLine.Tint = Color.LimeGreen; MapDatabaseCache.ForceUpdateMaps(); break; default: DividerLine.Tint = Color.Crimson; TopLine.Tint = Color.Crimson; break; } Header.Text = StatusCodeMessages[Response.Code]; } catch (Exception e) { Logger.Error(e, LogType.Network); DividerLine.Tint = Color.Crimson; TopLine.Tint = Color.Crimson; Header.Text = "An unknown error has occurred while uploading. Please check your log files!"; } finally { LoadingWheel.Visible = false; CreateCloseButton(); if (Response != null && (Response.Code == MapsetSubmissionStatusCode.SuccessUpdated || Response.Code == MapsetSubmissionStatusCode.SuccessUploaded)) { CloseButton.Border.Tint = Color.LimeGreen; CloseButton.Text.Tint = Color.LimeGreen; CreateVisitMapsetPageButton(); VisitMapsetPageButton.X = -VisitMapsetPageButton.Width / 2f - 10; CloseButton.X = CloseButton.Width / 2f + 10; } } });
/// <summary> /// </summary> public void UpdateContent() { Map map; if (MapManager.Selected.Value?.Md5Checksum == Game.MapMd5) { map = MapManager.Selected.Value; } else { map = MapManager.FindMapFromMd5(Game.MapMd5); // In the event that we don't have the correct version, try to find the // alternative one. This is commonly used for situations where one has osu! // beatmaps auto-loaded and someone downloads and converts the file to .qua format if (map == null && Game.MapMd5 != Game.AlternativeMd5) { map = MapManager.FindMapFromMd5(Game.AlternativeMd5); } MapManager.Selected.Value = map; } HasMap = map != null; if (OnlineManager.CurrentGame.HostSelectingMap) { ArtistTitle.Text = "Host is currently selecting a map!"; Mode.Text = "Please wait..."; Creator.Text = ""; DifficultyName.Text = ""; DifficultyRating.Text = ""; } else { var diffName = GetDifficultyName(); ArtistTitle.Text = Game.Map.Replace($"[{diffName}]", ""); Mode.Text = $"[{ModeHelper.ToShortHand((GameMode) Game.GameMode)}]"; Creator.Tint = Color.White; DifficultyRating.Text = map != null ? $"{map.DifficultyFromMods(ModManager.Mods):0.00}" : $"{Game.DifficultyRating:0.00}"; DifficultyRating.Tint = ColorHelper.DifficultyToColor((float)(map?.DifficultyFromMods(ModManager.Mods) ?? Game.DifficultyRating)); DifficultyRating.X = Mode.X + Mode.Width + 8; DifficultyName.X = DifficultyRating.X + DifficultyRating.Width + 2; DifficultyName.Text = " - \"" + diffName + "\""; } var game = (QuaverGame)GameBase.Game; if (map != null) { ArtistTitle.Tint = Color.White; var length = TimeSpan.FromMilliseconds(map.SongLength / ModHelper.GetRateFromMods(ModManager.Mods)); var time = length.Hours > 0 ? length.ToString(@"hh\:mm\:ss") : length.ToString(@"mm\:ss"); if (OnlineManager.CurrentGame.HostSelectingMap) { Creator.Text = ""; } else { Creator.Text = $"By: {map.Creator} | Length: {time} | BPM: {(int) (map.Bpm * ModHelper.GetRateFromMods(ModManager.Mods))} " + $"| LNs: {(int) map.LNPercentage}%"; } // Inform the server that we now have the map if we didn't before. if (OnlineManager.CurrentGame.PlayersWithoutMap.Contains(OnlineManager.Self.OnlineUser.Id)) { OnlineManager.Client.HasMultiplayerGameMap(); } if (game.CurrentScreen.Type == QuaverScreenType.Lobby || game.CurrentScreen.Type == QuaverScreenType.Multiplayer || QuaverScreenManager.QueuedScreen.Type == QuaverScreenType.Multiplayer || AudioEngine.Map != map) { if (BackgroundHelper.Map != MapManager.Selected.Value) { Background.Alpha = 0; var view = Screen.View as MultiplayerScreenView; view?.FadeBackgroundOut(); BackgroundHelper.Load(map); } ThreadScheduler.Run(() => { try { if (AudioEngine.Map != map) { if (!HasMap) { return; } AudioEngine.LoadCurrentTrack(); AudioEngine.Track.Play(); } } catch (Exception e) { // ignored } }); } } // Let the server know that we don't have the selected map else { ArtistTitle.Tint = Color.Red; Creator.Text = Game.MapId != -1 ? "You don't have this map. Click to download!" : "You don't have this map. Download not available!"; Creator.Tint = Colors.SecondaryAccent; if (!OnlineManager.CurrentGame.PlayersWithoutMap.Contains(OnlineManager.Self.OnlineUser.Id)) { OnlineManager.Client.DontHaveMultiplayerGameMap(); } if (!AudioEngine.Track.IsStopped) { AudioEngine.Track.Stop(); } MapManager.Selected.Value = MapManager.Mapsets.First().Maps.First(); } }
/// <summary> /// Selects a random map to be selected. (and for the track to play.) /// </summary> private void SelectNextTrack(Direction direction) { var game = (QuaverGame)GameBase.Game; if (MapManager.Mapsets.Count == 0 || game.CurrentScreen.Exiting) { return; } try { switch (direction) { case Direction.Forward: // We ran out of songs to play in the queue, so we need to pick a random one. if (TrackListQueuePosition == TrackListQueue.Count - 1) { var randomSet = RNG.Next(0, MapManager.Mapsets.Count); var randomMap = RNG.Next(0, MapManager.Mapsets[randomSet].Maps.Count); MapManager.Selected.Value = MapManager.Mapsets[randomSet].Maps[randomMap]; TrackListQueue.Add(MapManager.Selected.Value); } else { MapManager.Selected.Value = TrackListQueue[TrackListQueuePosition + 1]; } TrackListQueuePosition++; break; case Direction.Backward: // Don't allow backwards skipping if there arent any tracks before. if (TrackListQueuePosition - 1 < 0) { return; } TrackListQueuePosition--; MapManager.Selected.Value = TrackListQueue[TrackListQueuePosition]; break; default: throw new ArgumentOutOfRangeException(nameof(direction), direction, null); } LoadingNextTrack = true; ThreadScheduler.Run(() => { try { AudioEngine.LoadCurrentTrack(); if (AudioEngine.Track != null) { lock (AudioEngine.Track) AudioEngine.Track.Play(); } } catch (Exception e) { Logger.Error($"Track for map: could not be loaded.", LogType.Runtime); } finally { LoadingNextTrack = false; LoadFailures = 0; } // Update the song title's text with the new one. UpdateSongTitleText(); // Clear current song title animations. lock (SongTitleText.Animations) SongTitleText.Animations.Clear(); Logger.Debug($"Selected new jukebox track ({TrackListQueuePosition}): " + $"{MapManager.Selected.Value.Artist} - {MapManager.Selected.Value.Title} " + $"[{MapManager.Selected.Value.DifficultyName}] ", LogType.Runtime); ChangeDiscordPresenceToSongTitle(); }); } catch (Exception e) { Logger.Error(e, LogType.Runtime); LoadFailures++; // If we fail to load a track 3 times, then turn off the jukebox... if (LoadFailures == 3) { LoadingNextTrack = true; Logger.Error($"Failed to load track 3 times in a row. Stopping Jukebox", LogType.Runtime); } ChangeDiscordPresenceToIdle(); } }
/// <summary> /// Called when the user wants to change their selected beat snap. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnBeatSnapChanged(object sender, BindableValueChangedEventArgs <int> e) => ThreadScheduler.Run(() => { InitializeLines(); RecolorLines(); ShowMeasureLineIfApplicable(); });
/// <summary> /// Goes through the score submission process. /// saves score to local database. /// </summary> private void SubmitScore() { // Don't save scores if the user quit themself. if (Gameplay.HasQuit || Gameplay.InReplayMode) { return; } // Don't submit scores at all if the user has ALL misses for their judgements. // They basically haven't actually played the map. if (Gameplay.Ruleset.ScoreProcessor.CurrentJudgements[Judgement.Miss] == Gameplay.Ruleset.ScoreProcessor.TotalJudgementCount) { return; } ThreadScheduler.Run(SaveLocalScore); // Don't submit scores if disconnected from the server completely. if (OnlineManager.Status.Value == ConnectionStatus.Disconnected) { return; } if (Gameplay.IsMultiplayerGame) { if (!Gameplay.IsPlayComplete) { Logger.Important($"Skipping score submission due to play not being complete in multiplayer match", LogType.Network); return; } } if (Gameplay.Ruleset.ScoreProcessor.Failed && !Gameplay.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Failed) { Logger.Important($"Skipping score submission due to failing on custom windows, but not on standardized", LogType.Network); return; } ThreadScheduler.Run(() => { Logger.Important($"Beginning to submit score on map: {Gameplay.MapHash}", LogType.Network); var map = Map; var submissionMd5 = Gameplay.MapHash; if (map.Game != MapGame.Quaver) { submissionMd5 = map.GetAlternativeMd5(); } // For any unsubmitted maps, ask the server if it has the .qua already cached // if it doesn't, then we need to provide it. if (map.RankedStatus == RankedStatus.NotSubmitted && OnlineManager.IsDonator) { var info = OnlineManager.Client?.RetrieveMapInfo(submissionMd5); // Map is not uploaded, so we have to provide the server with it. if (info == null) { Logger.Important($"Unsubmitted map is not cached on the server. Need to provide!", LogType.Network); var success = OnlineManager.Client?.UploadUnsubmittedMap(Gameplay.Map, submissionMd5, map.Md5Checksum); // The map upload wasn't successful, so we can assume that our score shouldn't be submitted if (success != null && !success.Value) { Logger.Error($"Unsubmitted map upload was not successful. Skipping score submission", LogType.Network); return; } } } OnlineManager.Client?.Submit(new OnlineScore(submissionMd5, Gameplay.ReplayCapturer.Replay, Gameplay.Ruleset.StandardizedReplayPlayer.ScoreProcessor, ScrollSpeed, ModHelper.GetRateFromMods(ModManager.Mods), TimeHelper.GetUnixTimestampMilliseconds(), SteamManager.PTicket)); }); }
/// <summary> /// Starts the deleting process. /// </summary> public void DeleteSelected() { // Externally loaded map check. if (MapManager.Selected.Value.Game != MapGame.Quaver) { // Display error message. NotificationManager.Show(NotificationLevel.Error, "This map was loaded from another game, and it cannot be deleted."); return; } var view = View as SelectScreenView; var type = view.ActiveContainer; var selectedMapsetIndex = view.MapsetScrollContainer.SelectedMapsetIndex; var selectedDifficultyIndex = view.DifficultyScrollContainer.SelectedMapIndex; var selectedMapset = AvailableMapsets[selectedMapsetIndex]; var selectedDifficulty = selectedMapset.Maps[selectedDifficultyIndex]; var mapsetPath = Path.Combine(ConfigManager.SongDirectory.Value, selectedMapset.Directory); var difficultyPath = Path.Combine(mapsetPath, selectedDifficulty.Path); // Commence deleting and reloading. var confirmDelete = new ConfirmCancelDialog($"Are you sure you want to delete this {( type == SelectContainerStatus.Mapsets ? "mapset" : "difficulty" )}?", (sender, confirm) => { var mapTitle = selectedMapset.Maps.First().Title; var deleteMapset = type == SelectContainerStatus.Mapsets || type == SelectContainerStatus.Difficulty && selectedMapset.Maps.Count == 1; // Dispose of the background for the currently selected map. BackgroundHelper.Background?.Dispose(); // Dispose of the currently playing track. AudioEngine.Track?.Dispose(); // Run path deletion in the background. ThreadScheduler.Run(() => DeletePath(deleteMapset ? mapsetPath : difficultyPath)); // Remove mapset/difficulty from cache and AvailableMapsets list. if (deleteMapset) { selectedMapset.Maps.ForEach(MapDatabaseCache.RemoveMap); AvailableMapsets.RemoveAt(selectedMapsetIndex); MapManager.Mapsets.RemoveAll(x => x.Directory == selectedMapset.Directory); view.MapsetScrollContainer.InitializeWithNewSets(); view.MapsetScrollContainer.SelectMapset(Math.Min(selectedMapsetIndex, AvailableMapsets.Count - 1)); } else { MapDatabaseCache.RemoveMap(selectedDifficulty); selectedMapset.Maps.RemoveAt(selectedDifficultyIndex); MapManager.Mapsets.Find(x => x.Directory == selectedMapset.Directory).Maps.RemoveAll(x => x.Md5Checksum == selectedDifficulty.Md5Checksum); view.DifficultyScrollContainer.ReInitializeDifficulties(); view.MapsetScrollContainer.SelectMap(selectedMapsetIndex, selectedMapset.Maps[Math.Min(selectedDifficultyIndex, selectedMapset.Maps.Count - 1)], true); } // Finally show confirmation notification. NotificationManager.Show(NotificationLevel.Success, $"Successfully deleted {mapTitle} from Quaver!"); // If the deleted mapset was the last one, then exit back to menu. if (MapManager.Mapsets.Count != 0) { return; } view.Destroy(); AudioEngine.Track = null; MapManager.Selected.Value = null; ExitToMenu(); }); // Finally show the confirmation dialog that orchestrates the deleting process. DialogManager.Show(confirmDelete); }