/// <summary> /// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime. /// </summary> /// <exception cref="InvalidOperationException">Thrown if this method is called more than once without changing state.</exception> private void scoreCompletionChanged(ValueChangedEvent <bool> completed) { // If this player instance is in the middle of an exit, don't attempt any kind of state update. if (!this.IsCurrentScreen()) { return; } // Special case to handle rewinding post-completion. This is the only way already queued forward progress can be cancelled. // TODO: Investigate whether this can be moved to a RewindablePlayer subclass or similar. // Currently, even if this scenario is hit, prepareScoreForDisplay has already been queued (and potentially run). // In scenarios where rewinding is possible (replay, spectating) this is a non-issue as no submission/import work is done, // but it still doesn't feel right that this exists here. if (!completed.NewValue) { resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; GameplayState.HasPassed = false; ValidForResume = true; skipOutroOverlay.Hide(); return; } // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) { return; } GameplayState.HasPassed = true; // Setting this early in the process means that even if something were to go wrong in the order of events following, there // is no chance that a user could return to the (already completed) Player instance from a child screen. ValidForResume = false; // Ensure we are not writing to the replay any more, as we are about to consume and store the score. DrawableRuleset.SetRecordTarget(null); if (!Configuration.ShowResults) { return; } prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { // if the current beatmap has a storyboard, the progression to results will be handled by the storyboard ending // or the user pressing the skip outro button. skipOutroOverlay.Show(); return; } progressToResults(true); }
private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); if (Beatmap.Value is DummyWorkingBeatmap) { return; } IBeatmap playableBeatmap = loadPlayableBeatmap(); if (playableBeatmap == null) { return; } sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable <bool>(OsuSetting.MouseDisableWheel); if (game != null) { LocalUserPlaying.BindTo(game.LocalUserPlaying); } DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); if (!ScoreProcessor.Mode.Disabled) { config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); } InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); dependencies.CacheAs(gameplayBeatmap); var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation // full access to all skin sources. var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider)); rulesetSkinProvider.AddRange(new[] { // underlay and gameplay should have access the to skinning sources. createUnderlayComponents(), createGameplayComponents(Beatmap.Value, playableBeatmap) }); // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); if (!DrawableRuleset.AllowGameplayOverlays) { HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); skipOverlay.Hide(); } DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => { if (waiting.NewValue) { GameplayClockContainer.Stop(); } else { GameplayClockContainer.Start(); } }); DrawableRuleset.IsPaused.BindValueChanged(paused => { updateGameplayState(); updateSampleDisabledState(); }); DrawableRuleset.FrameStableClock.IsCatchingUp.BindValueChanged(_ => updateSampleDisabledState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); DrawableRuleset.NewResult += r => { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); gameplayBeatmap.ApplyResult(r); }; DrawableRuleset.RevertResult += r => { HealthProcessor.RevertResult(r); ScoreProcessor.RevertResult(r); }; // Bind the judgement processors to ourselves ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType <IApplicableToScoreProcessor>()) { mod.ApplyToScoreProcessor(ScoreProcessor); } foreach (var mod in Mods.Value.OfType <IApplicableToHealthProcessor>()) { mod.ApplyToHealthProcessor(HealthProcessor); } IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindValueChanged(onBreakTimeChanged, true); }
private void load(AudioManager audio, OsuConfigManager config) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); if (Beatmap.Value is DummyWorkingBeatmap) { return; } IBeatmap playableBeatmap = loadPlayableBeatmap(); if (playableBeatmap == null) { return; } sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable <bool>(OsuSetting.MouseDisableWheel); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); if (!ScoreProcessor.Mode.Disabled) { config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); } InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); dependencies.CacheAs(gameplayBeatmap); addUnderlayComponents(GameplayClockContainer); addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); addOverlayComponents(GameplayClockContainer, Beatmap.Value); if (!DrawableRuleset.AllowGameplayOverlays) { HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); skipOverlay.Hide(); } DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); DrawableRuleset.OnNewResult += r => { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); gameplayBeatmap.ApplyResult(r); }; DrawableRuleset.OnRevertResult += r => { HealthProcessor.RevertResult(r); ScoreProcessor.RevertResult(r); }; // Bind the judgement processors to ourselves ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType <IApplicableToScoreProcessor>()) { mod.ApplyToScoreProcessor(ScoreProcessor); } foreach (var mod in Mods.Value.OfType <IApplicableToHealthProcessor>()) { mod.ApplyToHealthProcessor(HealthProcessor); } breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); }
/// <summary> /// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime. /// </summary> /// <param name="skipStoryboardOutro">If in a state where a storyboard outro is to be played, offers the choice of skipping beyond it.</param> /// <exception cref="InvalidOperationException">Thrown if this method is called more than once without changing state.</exception> private void updateCompletionState(bool skipStoryboardOutro = false) { // screen may be in the exiting transition phase. if (!this.IsCurrentScreen()) { return; } if (!ScoreProcessor.HasCompleted.Value) { completionProgressDelegate?.Cancel(); completionProgressDelegate = null; ValidForResume = true; skipOutroOverlay.Hide(); return; } if (completionProgressDelegate != null) { throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once"); } // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) { return; } ValidForResume = false; // ensure we are not writing to the replay any more, as we are about to consume and store the score. DrawableRuleset.SetRecordTarget(null); if (!Configuration.ShowResults) { return; } prepareScoreForDisplayTask ??= Task.Run(async() => { PrepareScoreForResults(); try { await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, "Score preparation failed!"); } try { await ImportScore(Score).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, "Score import failed!"); } return(Score.ScoreInfo); }); if (skipStoryboardOutro) { scheduleCompletion(); return; } bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { skipOutroOverlay.Show(); return; } using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) scheduleCompletion(); }