/// <summary> /// Skips the song to the next object if on a break. /// </summary> private void SkipToNextObject() { if (!EligibleToSkip || IsPaused || IsResumeInProgress) { return; } // Get the skip time of the next object. var nextObject = Ruleset.HitObjectManager.NextHitObject.StartTime; var skipTime = nextObject - GameplayAudioTiming.StartDelay * ModHelper.GetRateFromMods(ModManager.Mods); try { // Skip to the time if the audio already played once. If it hasn't, then play it. AudioEngine.Track?.Seek(skipTime); Timing.Time = AudioEngine.Track.Time; } catch (Exception e) { Logger.Error(e, LogType.Runtime); Logger.Warning("Trying to skip with no audio file loaded. Still continuing..", LogType.Runtime); Timing.Time = skipTime; } finally { if (InReplayMode) { var inputManager = (KeysInputManager)Ruleset.InputManager; inputManager.ReplayInputManager.HandleSkip(); } } }
/// <summary> /// Ctor /// </summary> /// <param name="screen"></param> public GameplayAudioTiming(GameplayScreen screen) { Screen = screen; try { if (Screen.IsCalibratingOffset) { AudioEngine.Track = new AudioTrack(GameBase.Game.Resources.Get($"Quaver.Resources/Maps/Offset/offset.mp3")); } else { AudioEngine.LoadCurrentTrack(); AudioEngine.Track.Rate = ModHelper.GetRateFromMods(ModManager.Mods); } if (Screen.IsPlayTesting) { AudioEngine.Track.Seek(Screen.PlayTestAudioTime); Time = AudioEngine.Track.Time; return; } } catch (AudioEngineException e) { Logger.Error(e, LogType.Runtime); } // Set the base time to - the start delay. Time = -StartDelay * AudioEngine.Track.Rate; }
/// <summary> /// Initializes the mods for this given play. /// (Recalculates hit windows.) /// </summary> private void InitializeMods() { for (var i = 0; i < JudgementWindow.Count; i++) { JudgementWindow[(Judgement)i] *= ModHelper.GetRateFromMods(Mods); } }
/// <summary> /// Updates the text of the metadata with a new map/mods /// Realigns it so it's in the middle. /// </summary> /// <param name="map"></param> public void UpdateAndAlignMetadata(Map map) { var length = TimeSpan.FromMilliseconds(map.SongLength / ModHelper.GetRateFromMods(ModManager.Mods)); Mode.UpdateValue(ModeHelper.ToShortHand(map.Mode)); Bpm.UpdateValue(((int)(map.Bpm * ModHelper.GetRateFromMods(ModManager.Mods))).ToString(CultureInfo.InvariantCulture)); Length.UpdateValue(length.Hours > 0 ? length.ToString(@"hh\:mm\:ss") : length.ToString(@"mm\:ss")); Difficulty.UpdateValue(StringHelper.AccuracyToString((float) map.DifficultyFromMods(ModManager.Mods)).Replace("%", "")); LNPercentage.UpdateValue(((int) map.LNPercentage).ToString(CultureInfo.InvariantCulture) + "%"); for (var i = 0; i < Items.Count; i++) { var metadata = Items[i]; if (i == 0) { metadata.X = 5; continue; } var previous = Items[i - 1]; metadata.X = previous.X + previous.Width + 5 + 5; } Items.ForEach(x => x.X += (Banner.Width - Items.Last().X) / Items.Count / 2); }
/// <inheritdoc /> /// <summary> /// </summary> /// <param name="gameTime"></param> public override void Update(GameTime gameTime) { Bindable.Value = Screen.Timing.Time / ModHelper.GetRateFromMods(ModManager.Mods); // Only update time each second. if (Screen.Timing.Time - TimeLastProgressChange < 1000) { base.Update(gameTime); return; } TimeLastProgressChange = Screen.Timing.Time; // Set the time of the current time if (ConfigManager.DisplaySongTimeProgressNumbers.Value) { if (Bindable.Value > 0) { var currTime = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds((int)Bindable.Value); CurrentTime.Value = currTime.ToString("mm:ss"); } // Set the time of the time left. if (Bindable.MaxValue - Bindable.Value >= 0) { var timeLeft = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds((int)Bindable.MaxValue - Bindable.Value); // Set the new value. TimeLeft.Value = "-" + timeLeft.ToString("mm:ss"); } } base.Update(gameTime); }
/// <summary> /// Makes sure that the speed gameplayModifier selected matches up with the game clock and sets the correct one. /// </summary> private static void CheckModInconsistencies() { try { if (AudioEngine.Track != null && !AudioEngine.Track.IsDisposed) { AudioEngine.Track.Rate = ModHelper.GetRateFromMods(Mods); } } catch (Exception e) { // ignored. } }
/// <summary> /// Adds a judgement to the hit error at a given hit time. /// </summary> public void AddJudgement(Judgement j, double hitTime) { CurrentLinePoolIndex++; if (CurrentLinePoolIndex >= PoolSize) { CurrentLinePoolIndex = 0; } LineObjectPool[CurrentLinePoolIndex].Tint = SkinManager.Skin.Keys[MapManager.Selected.Value.Mode].JudgeColors[j]; LineObjectPool[CurrentLinePoolIndex].X = -(float)hitTime / ModHelper.GetRateFromMods(ModManager.Mods); LineObjectPool[CurrentLinePoolIndex].Alpha = 0.5f; }
/// <summary> /// Skips the song to the next object if on a break. /// </summary> private void SkipToNextObject(bool force = false) { if (!EligibleToSkip || IsPaused || IsResumeInProgress) { return; } if (IsMultiplayerGame && !force) { if (RequestedToSkipSong) { return; } OnlineManager.Client?.RequestToSkipSong(); RequestedToSkipSong = true; NotificationManager.Show(NotificationLevel.Info, "Requested to skip song. Waiting for all other players to skip!"); return; } // Get the skip time of the next object. var nextObject = Ruleset.HitObjectManager.NextHitObject.StartTime; var skipTime = nextObject - GameplayAudioTiming.StartDelay * ModHelper.GetRateFromMods(ModManager.Mods); try { // Skip to the time if the audio already played once. If it hasn't, then play it. AudioEngine.Track?.Seek(skipTime); Timing.Time = AudioEngine.Track.Time; } catch (Exception e) { Logger.Error(e, LogType.Runtime); Logger.Warning("Trying to skip with no audio file loaded. Still continuing..", LogType.Runtime); Timing.Time = skipTime; } finally { if (InReplayMode) { var inputManager = (KeysInputManager)Ruleset.InputManager; inputManager.ReplayInputManager.HandleSkip(); } // Stop all playing sound effects and move NextSoundEffectIndex ahead. CustomAudioSampleCache.StopAll(); UpdateNextSoundEffectIndex(); } }
/// <summary> /// Gets the adjacent rate value. /// /// For example, if the current rate is 1.0x, the adjacent value would be either 0.95x or 1.1x, /// depending on the argument. /// </summary> /// <param name="faster">If true, returns the higher rate, otherwise the lower rate.</param> /// <returns></returns> private static float GetNextRate(bool faster) { var current = ModHelper.GetRateFromMods(ModManager.Mods); var adjustment = 0.1f; // ReSharper disable once CompareOfFloatsByEqualityOperator if (current < 1.0f || (current == 1.0f && !faster)) { adjustment = 0.05f; } var next = current + adjustment * (faster ? 1f : -1f); return((float)Math.Round(next, 2)); }
/// <summary> /// Initializes the mods for this given play. /// (Recalculates hit windows.) /// </summary> private void InitializeMods() { for (var i = 0; i < JudgementWindow.Count; i++) { JudgementWindow[(Judgement)i] *= ModHelper.GetRateFromMods(Mods); } if (Mods.HasFlag(ModIdentifier.HeatlthAdjust)) { JudgementHealthWeighting[Judgement.Marv] = 0.65f; JudgementHealthWeighting[Judgement.Perf] = 0.45f; JudgementHealthWeighting[Judgement.Great] = 0.3f; JudgementHealthWeighting[Judgement.Good] = 0.1f; JudgementHealthWeighting[Judgement.Okay] = -2.5f; JudgementHealthWeighting[Judgement.Miss] = -5.0f; } }
/// <summary> /// Loads the track for the currently selected map. /// </summary> public static void LoadCurrentTrack() { Map = MapManager.Selected.Value; if (Track != null && !Track.IsDisposed) { Track.Dispose(); } Track = new AudioTrack(MapManager.CurrentAudioPath) { Volume = ConfigManager.VolumeMusic.Value, Rate = ModHelper.GetRateFromMods(ModManager.Mods), }; Track.ToggleRatePitching(ConfigManager.Pitched.Value); }
/// <summary> /// Creates the early and late text labels. /// </summary> private void CreateEarlyLateText() { var unscaledLargestHitWindow = LargestHitWindow / ModHelper.GetRateFromMods(Processor.Mods); // ReSharper disable once ObjectCreationAsStatement new SpriteText(Fonts.SourceSansProSemiBold, $"Late (+{unscaledLargestHitWindow}ms)", 13) { Parent = this, X = 2 }; // ReSharper disable once ObjectCreationAsStatement new SpriteText(Fonts.SourceSansProSemiBold, $"Early (-{unscaledLargestHitWindow}ms)", 13) { Parent = this, Alignment = Alignment.BotLeft, X = 2 }; }
/// <inheritdoc /> /// <summary> /// </summary> /// <param name="gameTime"></param> public override void Update(GameTime gameTime) { Bindable.Value = Screen.Timing.Time / ModHelper.GetRateFromMods(ModManager.Mods); // Only update time each second. if (Screen.Timing.Time - TimeLastProgressChange < 1000) { base.Update(gameTime); return; } TimeLastProgressChange = Screen.Timing.Time; // Set the time of the current time if (ConfigManager.DisplaySongTimeProgressNumbers.Value) { if (Bindable.Value > 0) { var currTime = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds((int)Bindable.Value); CurrentTime.Value = currTime.ToString("mm:ss"); } // Set the time of the time left. if (Bindable.MaxValue - Bindable.Value >= 0) { var timeLeft = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds((int)Bindable.MaxValue - Bindable.Value); // Get the old value. var oldValue = TimeLeft.Value; // Set the new value. TimeLeft.Value = "-" + timeLeft.ToString("mm:ss"); // Check if we need to reposition it since it's on the right side of the screen. if (oldValue.Length != TimeLeft.Value.Length) { TimeLeft.X = -TimeLeft.TotalWidth - 10; } } } base.Update(gameTime); }
/// <summary> /// Computes the hit statistics from the hit data. /// </summary> /// <returns></returns> public HitStatistics GetHitStatistics() { var largestHitWindow = JudgementWindow.Values.Max(); var hitDifferences = new List <int>(); var sum = 0; foreach (var breakdown in Stats) { var hitDifference = breakdown.HitDifference; // The LN releases are _not_ scaled here because we want an accurate mean. // No need to check for Type == Miss as all of them have hitDifference == int.MinValue. if (hitDifference != int.MinValue && Math.Abs(hitDifference) <= largestHitWindow) { hitDifferences.Add(hitDifference); sum += hitDifference; // This overflows at like 13 million max judgements. } } double mean = 0.0; double standardDeviation = 0.0; var count = hitDifferences.Count(); if (count > 0) { mean = (double)sum / (double)count; standardDeviation = Math.Sqrt(hitDifferences.Average(v => Math.Pow(v - mean, 2))); // Undo the rate scaling. mean /= ModHelper.GetRateFromMods(Mods); // Since variance(ax) = a^2 variance(x), then std(ax) = a std(x) standardDeviation /= ModHelper.GetRateFromMods(Mods); } return(new HitStatistics { Mean = mean, StandardDeviation = standardDeviation }); }
/// <summary> /// Removes any speed mods from the game and resets the clock /// </summary> public static void RemoveSpeedMods() { try { CurrentModifiersList.RemoveAll(x => x.Type == ModType.Speed); if (AudioEngine.Track != null) { AudioEngine.Track.Rate = ModHelper.GetRateFromMods(Mods); } CheckModInconsistencies(); ModsChanged?.Invoke(typeof(ModManager), new ModsChangedEventArgs(Mods)); Logger.Debug("Removed all speed modifiers", LogType.Runtime, false); } catch (Exception e) { Logger.Error(e, LogType.Runtime); } }
/// <summary> /// Calculates the final health weighting values for each judgement from /// average actions per second. <see cref="Qua.GetActionsPerSecond"/> /// /// Resource: https://www.desmos.com/calculator/veeobxirvz /// </summary> protected override void InitializeHealthWeighting() { if (Mods.HasFlag(ModIdentifier.Autoplay)) { return; } var density = Map.GetActionsPerSecond(ModHelper.GetRateFromMods(Mods)); // ReSharper disable once CompareOfFloatsByEqualityOperator if (density == 0 || density >= 12 || double.IsNaN(density)) { return; } // Set baseline density to 2 if (density > 0 && density < 2) { density = 2; } var values = new Dictionary <Judgement, Tuple <float, float> > { { Judgement.Marv, new Tuple <float, float>(-0.14f, 2.68f) }, { Judgement.Perf, new Tuple <float, float>(-0.2f, 3.4f) }, { Judgement.Great, new Tuple <float, float>(-0.14f, 2.68f) }, { Judgement.Good, new Tuple <float, float>(0.084f, -0.008f) }, { Judgement.Okay, new Tuple <float, float>(0.081f, 0.028f) } }; foreach (var item in values) { var val = values[item.Key]; var multiplier = val.Item1 * density + val.Item2; var weight = JudgementHealthWeighting[item.Key]; JudgementHealthWeighting[item.Key] = (float)Math.Round(multiplier * weight, 2, MidpointRounding.ToEven); } }
/// <summary> /// Loads the track for the currently selected map. /// </summary> public static void LoadCurrentTrack() { Map = MapManager.Selected.Value; if (Track != null && !Track.IsDisposed) { Track.Dispose(); } if (!File.Exists(MapManager.CurrentAudioPath)) { throw new FileNotFoundException($"The audio file at path: {MapManager.CurrentAudioPath} could not be found."); } Track = new AudioTrack(MapManager.CurrentAudioPath) { Volume = ConfigManager.VolumeMusic.Value, Rate = ModHelper.GetRateFromMods(ModManager.Mods), }; Track.ToggleRatePitching(ConfigManager.Pitched.Value); }
/// <summary> /// Gets the selected index of the speeds based on the audio rate /// </summary> /// <returns></returns> private int GetSelectedIndex() { var rate = ModHelper.GetRateFromMods(ModManager.Mods); string rateString; switch (rate) { case 1f: rateString = "1.0x"; break; case 2: rateString = "2.0x"; break; default: rateString = $"{rate}x"; break; } return(Speeds.FindIndex(x => x == rateString)); }
/// <summary> /// Loads the track for the currently selected map. /// </summary> public static void LoadCurrentTrack(bool preview = false, int time = 300000) { Source.Cancel(); Source.Dispose(); Source = new CancellationTokenSource(); Map = MapManager.Selected.Value; var token = Source.Token; try { if (Track != null && !Track.IsDisposed) { Track.Dispose(); } var newTrack = new AudioTrack(MapManager.CurrentAudioPath, false) { Rate = ModHelper.GetRateFromMods(ModManager.Mods), }; token.ThrowIfCancellationRequested(); Track = newTrack; Track.ApplyRate(ConfigManager.Pitched.Value); } catch (OperationCanceledException e) { } catch (Exception e) { if (Track != null && !Track.IsDisposed) { Track.Dispose(); } Track = new AudioTrackVirtual(time); } }
/// <summary> /// Creates the early and late text labels. /// </summary> private void CreateEarlyLateText() { var unscaledLargestHitWindow = LargestHitWindow / ModHelper.GetRateFromMods(Processor.Mods); // ReSharper disable once ObjectCreationAsStatement new SpriteTextBitmap(FontsBitmap.GothamRegular, $"Late (+{unscaledLargestHitWindow} ms)") { Parent = this, X = 4, Y = 5, FontSize = 14 }; // ReSharper disable once ObjectCreationAsStatement new SpriteTextBitmap(FontsBitmap.GothamRegular, $"Early (-{unscaledLargestHitWindow} ms)") { Parent = this, Alignment = Alignment.BotLeft, X = 4, Y = -5, FontSize = 14 }; }
/// <summary> /// Calculate difficulty of a map with given rate /// </summary> /// <param name="rate"></param> public void CalculateDifficulty(ModIdentifier mods) { // If map does not exist, ignore calculation. if (Map == null) { return; } // Get song rate from selected mods var rate = ModHelper.GetRateFromMods(mods); // Compute for overall difficulty switch (Map.Mode) { case (GameMode.Keys4): OverallDifficulty = ComputeForOverallDifficulty(rate); break; case (GameMode.Keys7): OverallDifficulty = (ComputeForOverallDifficulty(rate, Hand.Left) + ComputeForOverallDifficulty(rate, Hand.Right)) / 2; break; } }
/// <summary> /// Loads the track for the currently selected map. /// </summary> public static void LoadCurrentTrack(bool preview = false) { Source.Cancel(); Source.Dispose(); Source = new CancellationTokenSource(); Map = MapManager.Selected.Value; var token = Source.Token; try { if (Track != null && !Track.IsDisposed) { Track.Dispose(); } var newTrack = new AudioTrack(MapManager.CurrentAudioPath, preview) { Volume = ConfigManager.VolumeMusic.Value, Rate = ModHelper.GetRateFromMods(ModManager.Mods), }; token.ThrowIfCancellationRequested(); Track = newTrack; Track.ApplyRate(ConfigManager.Pitched.Value); } catch (OperationCanceledException e) { // ignored } catch (Exception e) { //Logger.Error(e, LogType.Runtime); } }
/// <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 (ScoreProcessor.CurrentJudgements[Judgement.Miss] == ScoreProcessor.TotalJudgementCount) { return; } ThreadScheduler.Run(SaveLocalScore); // Don't submit scores if disconnected from the server completely. if (OnlineManager.Status.Value == ConnectionStatus.Disconnected) { return; } ThreadScheduler.Run(() => { Logger.Important($"Beginning to submit score on map: {Gameplay.MapHash}", LogType.Network); OnlineManager.Client?.Submit(new OnlineScore(Gameplay.MapHash, Gameplay.ReplayCapturer.Replay, ScoreProcessor, ScrollSpeed, ModHelper.GetRateFromMods(ModManager.Mods), TimeHelper.GetUnixTimestampMilliseconds(), SteamManager.PTicket)); }); }
/// <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> /// </summary> private void CreateGraph() { var i = 0; foreach (Judgement j in Enum.GetValues(typeof(Judgement))) { if (j == Judgement.Ghost) { continue; } var color = SkinManager.Skin.Keys[GameMode.Keys4].JudgeColors[j]; var judgementName = new SpriteTextBitmap(FontsBitmap.GothamRegular, j.ToString().ToUpper()) { Parent = this, Y = i * 50 + 24, X = 15, Tint = color, FontSize = 16 }; var percentage = Processor.CurrentJudgements[j] == 0 ? 0 : (float)Processor.CurrentJudgements[j] / Processor.TotalJudgementCount; var progressBar = new Sprite { Parent = judgementName, Alignment = Alignment.MidLeft, Tint = color, Height = 14, Width = (Width - 300) * percentage, X = 80, }; if (progressBar.Width <= 1) { progressBar.Width = 1; } var judgementAmount = new SpriteTextBitmap(FontsBitmap.GothamRegular, $"{Processor.CurrentJudgements[j]:N0} ({percentage * 100:0.0}%)") { Parent = progressBar, Alignment = Alignment.MidLeft, Tint = color, X = progressBar.Width + 10, FontSize = 15 }; var windows = new SpriteTextBitmap(FontsBitmap.GothamRegular, $"") { Parent = progressBar, Alignment = Alignment.MidLeft, Tint = color, X = judgementAmount.X + judgementAmount.Width + 3, FontSize = 15 }; if (Container.StandardizedProcessor != null) { windows.Text = $" - {Processor.JudgementWindow[j] / ModHelper.GetRateFromMods(Processor.Mods)} ms"; } else if (Container.Screen.ResultsType == ResultScreenType.Score && Container.Screen.Score.JudgementWindowPreset != JudgementWindowsDatabaseCache.Standard.Name && Container.Screen.Score.JudgementWindowPreset != null) { switch (j) { case Judgement.Marv: windows.Text = " - " + Container.Screen.Score.JudgementWindowMarv; break; case Judgement.Perf: windows.Text = " - " + Container.Screen.Score.JudgementWindowPerf; break; case Judgement.Great: windows.Text = " - " + Container.Screen.Score.JudgementWindowGreat; break; case Judgement.Good: windows.Text = " - " + Container.Screen.Score.JudgementWindowGood; break; case Judgement.Okay: windows.Text = " - " + Container.Screen.Score.JudgementWindowOkay; break; case Judgement.Miss: windows.Text = " - " + Container.Screen.Score.JudgementWindowMiss; break; } windows.Text += " ms"; } i++; } }
/// <summary> /// Creates the progress bar if the user defined it in config. /// </summary> private void CreateProgressBar() { if (!ConfigManager.DisplaySongTimeProgress.Value) { return; } var skin = SkinManager.Skin.Keys[Screen.Map.Mode]; ProgressBar = new SongTimeProgressBar(Screen, new Vector2(WindowManager.Width, 4), 0, Screen.Map.Length / ModHelper.GetRateFromMods(ModManager.Mods), 0, skin.SongTimeProgressInactiveColor, skin.SongTimeProgressActiveColor) { Parent = Container, Alignment = Alignment.BotLeft }; }
/// <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> /// Closes the dialog. /// </summary> public void Close(bool changeMods = true) { if (isClosing) { return; } if (OnlineManager.CurrentGame != null && changeMods) { if (ModsWhenDialogOpen != ModManager.Mods) { var diffRating = OnlineManager.CurrentGame.DifficultyRating; // Only update the difficulty rating when closing if the selected map is still the same. // If the user switches maps, but changes modifiers, it'll be incorrect. if (MapManager.Selected.Value != null && MapManager.Selected.Value.Md5Checksum == OnlineManager.CurrentGame.MapMd5) { diffRating = MapManager.Selected.Value.DifficultyFromMods(ModManager.Mods); } var rateNow = ModHelper.GetRateFromMods(ModManager.Mods); // Change the global mods of the game var rateMod = (long)ModHelper.GetModsFromRate(rateNow); if (rateMod == -1) { rateMod = 0; } var activeModsWithoutRate = (long)ModManager.Mods - rateMod; ModIdentifier hostOnlyMods = 0L; var onlyHostChangeableMods = ModManager.CurrentModifiersList.FindAll(x => x.OnlyMultiplayerHostCanCanChange); if (onlyHostChangeableMods.Count != 0) { onlyHostChangeableMods.ForEach(x => { // ReSharper disable once AccessToModifiedClosure activeModsWithoutRate -= (long)x.ModIdentifier; hostOnlyMods |= x.ModIdentifier; }); } if (activeModsWithoutRate == -1) { activeModsWithoutRate = 0; } // If we're on regular free mod mode, when we change the rate, // ReSharper disable once CompareOfFloatsByEqualityOperator if (OnlineManager.CurrentGame.FreeModType == MultiplayerFreeModType.Regular && (ModHelper.GetRateFromMods(ModsWhenDialogOpen) != rateNow || hostOnlyMods != 0) && OnlineManager.CurrentGame.Host == OnlineManager.Self.OnlineUser) { OnlineManager.Client?.MultiplayerChangeGameModifiers(rateMod + (long)hostOnlyMods, diffRating); // Change the mods of ourselves minus the mods rate (gets all other activated modes) OnlineManager.Client?.MultiplayerChangePlayerModifiers(activeModsWithoutRate); } // Free mod is enabled, but we haven't done any rate changing, so just // enable player modifiers for us else if (OnlineManager.CurrentGame.FreeModType != MultiplayerFreeModType.None) { // Free Mod + Free Rate if (OnlineManager.CurrentGame.FreeModType.HasFlag(MultiplayerFreeModType.Regular) && OnlineManager.CurrentGame.FreeModType.HasFlag(MultiplayerFreeModType.Rate)) { if (OnlineManager.CurrentGame.Host == OnlineManager.Self.OnlineUser) { OnlineManager.Client?.MultiplayerChangeGameModifiers((long)hostOnlyMods, diffRating); } OnlineManager.Client?.MultiplayerChangePlayerModifiers((long)ModManager.Mods); } // Either Free Mod OR Free Rate else { switch (OnlineManager.CurrentGame.FreeModType) { case MultiplayerFreeModType.Regular: OnlineManager.Client?.MultiplayerChangePlayerModifiers(activeModsWithoutRate); if (OnlineManager.CurrentGame.Host == OnlineManager.Self.OnlineUser) { OnlineManager.Client?.MultiplayerChangeGameModifiers(rateMod + (long)hostOnlyMods, diffRating); } break; case MultiplayerFreeModType.Rate: OnlineManager.Client?.MultiplayerChangePlayerModifiers((long)ModHelper.GetModsFromRate(rateNow)); if (OnlineManager.CurrentGame.Host == OnlineManager.Self.OnlineUser) { OnlineManager.Client?.MultiplayerChangeGameModifiers(activeModsWithoutRate + (long)hostOnlyMods, diffRating); } break; } } } // We're host & free mod isn't enabled, so change the global game mods else if (OnlineManager.CurrentGame.Host == OnlineManager.Self.OnlineUser) { OnlineManager.Client?.MultiplayerChangeGameModifiers((long)ModManager.Mods, diffRating); } } } isClosing = true; InterfaceContainer.Animations.Clear(); InterfaceContainer.Animations.Add(new Animation(AnimationProperty.Y, Easing.OutQuint, InterfaceContainer.Y, InterfaceContainer.Height, 600)); ThreadScheduler.RunAfter(() => DialogManager.Dismiss(this), 450); }
/// <summary> /// Creates the local and global offset fix buttons. /// </summary> private void CreateOffsetFixButtons() { // Don't draw the buttons if we don't have the hit stats (and therefore don't know the // values to adjust the offset by). if (Processor.Stats == null) { return; } var availableWidth = Width - VerticalDividerLine.X; var buttonPadding = 15; var buttonWidth = (availableWidth - buttonPadding * 3) / 2; var localOffsetButton = new BorderedTextButton("Fix Local Offset", Colors.MainAccent, (o, e) => { // Local offset is scaled with rate, so the adjustment depends on the rate the // score was played on. var change = HitStatistics.Mean * ModHelper.GetRateFromMods(Processor.Mods); var newOffset = (int)Math.Round(ResultScreen.Map.LocalOffset - change); DialogManager.Show(new ConfirmCancelDialog($"Local offset will be changed from {ResultScreen.Map.LocalOffset} ms to {newOffset} ms.", (o_, e_) => { ResultScreen.Map.LocalOffset = newOffset; MapDatabaseCache.UpdateMap(ResultScreen.Map); NotificationManager.Show(NotificationLevel.Success, $"Local offset was set to {ResultScreen.Map.LocalOffset} ms."); })); }) { Parent = this, X = VerticalDividerLine.X + buttonPadding, Y = BottomHorizontalDividerLine.Y - 15 - 25, Height = 30, Width = buttonWidth, Text = { Font = Fonts.SourceSansProSemiBold, FontSize = 13 } }; var globalOffsetButton = new BorderedTextButton("Fix Global Offset", Colors.MainAccent, (o, e) => { var newOffset = (int)Math.Round(ConfigManager.GlobalAudioOffset.Value + HitStatistics.Mean); DialogManager.Show(new ConfirmCancelDialog($"Global offset will be changed from {ConfigManager.GlobalAudioOffset.Value} ms to {newOffset} ms.", (o_, e_) => { ConfigManager.GlobalAudioOffset.Value = newOffset; NotificationManager.Show(NotificationLevel.Success, $"Global offset was set to {ConfigManager.GlobalAudioOffset.Value} ms."); })); }) { Parent = this, X = localOffsetButton.X + localOffsetButton.Width + buttonPadding, Y = BottomHorizontalDividerLine.Y - 15 - 25, Height = 30, Width = buttonWidth, Text = { Font = Fonts.SourceSansProSemiBold, FontSize = 13 } }; }
/// <inheritdoc /> /// <summary> /// </summary> /// <param name="container"></param> /// <param name="item"></param> /// <param name="index"></param> /// <param name="header"></param> public ResultMultiplayerScoreboardUser(PoolableScrollContainer <ScoreboardUser> container, ScoreboardUser item, int index, ResultMultiplayerScoreboardTableHeader header) : base(container, item, index) { Header = header; Size = new ScalableVector2(container.Width, HEIGHT); Alpha = 0.85f; Button = new ResultMultiplayerScoreboardUserButton(Item, container) { Parent = this, Size = Size, UsePreviousSpriteBatchOptions = true }; // ReSharper disable once ObjectCreationAsStatement var rank = new SpriteTextBitmap(FontsBitmap.GothamRegular, $"{item.Rank}.") { Parent = this, Alignment = Alignment.MidLeft, X = 20, FontSize = 16, Tint = Item.Type == ScoreboardUserType.Self ? Colors.SecondaryAccent : Color.White, UsePreviousSpriteBatchOptions = true }; var avatar = new Sprite { Parent = this, Alignment = Alignment.MidLeft, Size = new ScalableVector2(32, 32), X = 56, Image = item.Avatar.Image, UsePreviousSpriteBatchOptions = true }; avatar.AddBorder(Color.White, 2); var username = new SpriteTextBitmap(FontsBitmap.GothamRegular, item.UsernameRaw) { Parent = this, Alignment = Alignment.MidLeft, X = avatar.X + avatar.Width + 16, FontSize = 16, Tint = Item.Type == ScoreboardUserType.Self ? Colors.SecondaryAccent : Color.White, UsePreviousSpriteBatchOptions = true }; if (item.Processor == null) { return; } CreateData(new Dictionary <string, string> { { "Rating", item.CalculateRating().ToString("00.00") }, { "Grade", "" }, { "Accuracy", StringHelper.AccuracyToString(item.Processor.Accuracy) }, { "Max Combo", item.Processor.MaxCombo + "x" }, { "Marv", item.Processor.CurrentJudgements[Judgement.Marv].ToString() }, { "Perf", item.Processor.CurrentJudgements[Judgement.Perf].ToString() }, { "Great", item.Processor.CurrentJudgements[Judgement.Great].ToString() }, { "Good", item.Processor.CurrentJudgements[Judgement.Good].ToString() }, { "Okay", item.Processor.CurrentJudgements[Judgement.Okay].ToString() }, { "Miss", item.Processor.CurrentJudgements[Judgement.Miss].ToString() }, { "Mods", item.Processor.Mods <= 0 ? "None" : ModHelper.GetModsString(ModHelper.GetModsFromRate(ModHelper.GetRateFromMods(item.Processor.Mods))) } }); // ReSharper disable once ObjectCreationAsStatement new Sprite { Parent = this, Alignment = Alignment.BotLeft, Size = new ScalableVector2(Width, 1), Alpha = 0.3f }; }