diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 05c8e835ac..71f9fafe57 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -29,6 +29,11 @@ namespace osu.Desktop.Updater
private static readonly Logger logger = Logger.GetLogger("updater");
+ ///
+ /// Whether an update has been downloaded but not yet applied.
+ ///
+ private bool updatePending;
+
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification)
{
@@ -37,9 +42,9 @@ namespace osu.Desktop.Updater
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
}
- protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
+ protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
- private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
+ private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
@@ -49,9 +54,19 @@ namespace osu.Desktop.Updater
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
+
if (info.ReleasesToApply.Count == 0)
+ {
+ if (updatePending)
+ {
+ // the user may have dismissed the completion notice, so show it again.
+ notificationOverlay.Post(new UpdateCompleteNotification(this));
+ return true;
+ }
+
// no updates available. bail and retry later.
- return;
+ return false;
+ }
if (notification == null)
{
@@ -72,6 +87,7 @@ namespace osu.Desktop.Updater
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
notification.State = ProgressNotificationState.Completed;
+ updatePending = true;
}
catch (Exception e)
{
@@ -103,6 +119,8 @@ namespace osu.Desktop.Updater
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
}
}
+
+ return true;
}
protected override void Dispose(bool isDisposing)
@@ -111,10 +129,27 @@ namespace osu.Desktop.Updater
updateManager?.Dispose();
}
+ private class UpdateCompleteNotification : ProgressCompletionNotification
+ {
+ [Resolved]
+ private OsuGame game { get; set; }
+
+ public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
+ {
+ Text = @"Update ready to install. Click to restart!";
+
+ Activated = () =>
+ {
+ updateManager.PrepareUpdateAsync()
+ .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
+ return true;
+ };
+ }
+ }
+
private class UpdateProgressNotification : ProgressNotification
{
private readonly SquirrelUpdateManager updateManager;
- private OsuGame game;
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
{
@@ -123,23 +158,12 @@ namespace osu.Desktop.Updater
protected override Notification CreateCompletionNotification()
{
- return new ProgressCompletionNotification
- {
- Text = @"Update ready to install. Click to restart!",
- Activated = () =>
- {
- updateManager.PrepareUpdateAsync()
- .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
- return true;
- }
- };
+ return new UpdateCompleteNotification(updateManager);
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, OsuGame game)
+ private void load(OsuColour colours)
{
- this.game = game;
-
IconContent.AddRange(new Drawable[]
{
new Box
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index bda595e840..ad584d3f48 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Catch
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
- public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
+ public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
public int LegacyID => 2;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index a4b9ca35eb..6a3a16ed33 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -25,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private int tinyTicksMissed;
private int misses;
- public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
- : base(ruleset, beatmap, score)
+ public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
+ : base(ruleset, attributes, score)
{
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 91383c5548..00bec18a45 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -29,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh;
private int countMiss;
- public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
- : base(ruleset, beatmap, score)
+ public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
+ : base(ruleset, attributes, score)
{
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d2feeb03af..b92e042686 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
- public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
+ public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score);
public const string SHORT_NAME = "mania";
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index a9879013f8..fff033357d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SpeedStrain;
public double ApproachRate;
public double OverallDifficulty;
+ public int HitCircleCount;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index b0d261a1cc..6027635b75 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
+ int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
+
return new OsuDifficultyAttributes
{
StarRating = starRating,
@@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo,
+ HitCircleCount = hitCirclesCount,
Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 02577461f0..063cde8747 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -5,11 +5,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -19,9 +17,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
- private readonly int countHitCircles;
- private readonly int beatmapMaxCombo;
-
private Mod[] mods;
private double accuracy;
@@ -31,14 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh;
private int countMiss;
- public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
- : base(ruleset, beatmap, score)
+ public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
+ : base(ruleset, attributes, score)
{
- countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
-
- beatmapMaxCombo = Beatmap.HitObjects.Count;
- // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
- beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
}
public override double Calculate(Dictionary categoryRatings = null)
@@ -81,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate);
- categoryRatings.Add("Max Combo", beatmapMaxCombo);
+ categoryRatings.Add("Max Combo", Attributes.MaxCombo);
}
return totalValue;
@@ -106,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Pow(0.97, countMiss);
// Combo scaling
- if (beatmapMaxCombo > 0)
- aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
+ if (Attributes.MaxCombo > 0)
+ aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
double approachRateFactor = 1.0;
@@ -154,8 +144,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= Math.Pow(0.97, countMiss);
// Combo scaling
- if (beatmapMaxCombo > 0)
- speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
+ if (Attributes.MaxCombo > 0)
+ speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
double approachRateFactor = 1.0;
if (Attributes.ApproachRate > 10.33)
@@ -178,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
double betterAccuracyPercentage;
- int amountHitObjectsWithAccuracy = countHitCircles;
+ int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index d946e7a113..678fb8aba6 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
- public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score);
+ public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index c04fffa2e7..2d9b95ae88 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh;
private int countMiss;
- public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
- : base(ruleset, beatmap, score)
+ public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
+ : base(ruleset, attributes, score)
{
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
index d5dd758e10..ac14e6131a 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -89,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
}
+ public override bool HandleMovement(MoveSelectionEvent moveEvent) => true;
+
protected override void UpdateTernaryStates()
{
base.UpdateTernaryStates();
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 93c9deec1f..a804ea5f82 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Audio;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
@@ -14,13 +15,29 @@ namespace osu.Game.Rulesets.Taiko.Skinning
{
public class TaikoLegacySkinTransformer : LegacySkinTransformer
{
+ private Lazy hasExplosion;
+
public TaikoLegacySkinTransformer(ISkinSource source)
: base(source)
{
+ Source.SourceChanged += sourceChanged;
+ sourceChanged();
+ }
+
+ private void sourceChanged()
+ {
+ hasExplosion = new Lazy(() => Source.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null);
}
public override Drawable GetDrawableComponent(ISkinComponent component)
{
+ if (component is GameplaySkinComponent)
+ {
+ // if a taiko skin is providing explosion sprites, hide the judgements completely
+ if (hasExplosion.Value)
+ return Drawable.Empty();
+ }
+
if (!(component is TaikoSkinComponent taikoComponent))
return null;
@@ -87,10 +104,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning
var hitName = getHitName(taikoComponent.Component);
var hitSprite = this.GetAnimation(hitName, true, false);
- var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
if (hitSprite != null)
+ {
+ var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
+
return new LegacyHitExplosion(hitSprite, strongHitSprite);
+ }
return null;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 7f8289aa91..73e9c16d07 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
- public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
+ public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score);
public int LegacyID => 1;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
index ce04b940e7..4fa4c00981 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@@ -23,33 +24,41 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestGameplayOverlayActivation()
{
+ AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
}
[Test]
public void TestGameplayOverlayActivationPaused()
{
+ AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause());
+ AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationReplayLoaded()
{
+ AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
+ AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationBreaks()
{
+ AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
+ AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
@@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer
{
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
+ public new Bindable LocalUserPlaying => base.LocalUserPlaying;
}
}
}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 207a3f01d3..78179a781a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -7,6 +7,7 @@ using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game.Input;
using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
@@ -69,6 +70,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.MouseDisableButtons, false);
Set(OsuSetting.MouseDisableWheel, false);
+ Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
// Graphics
Set(OsuSetting.ShowFpsDisplay, false);
@@ -194,6 +196,7 @@ namespace osu.Game.Configuration
FadePlayfieldWhenHealthLow,
MouseDisableButtons,
MouseDisableWheel,
+ ConfineMouseMode,
AudioOffset,
VolumeInactive,
MenuMusic,
diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs
new file mode 100644
index 0000000000..3dadae6317
--- /dev/null
+++ b/osu.Game/Input/ConfineMouseTracker.cs
@@ -0,0 +1,61 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Game.Configuration;
+
+namespace osu.Game.Input
+{
+ ///
+ /// Connects with .
+ /// If is true, we should also confine the mouse cursor if it has been
+ /// requested with .
+ ///
+ public class ConfineMouseTracker : Component
+ {
+ private Bindable frameworkConfineMode;
+ private Bindable osuConfineMode;
+ private IBindable localUserPlaying;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
+ {
+ frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode);
+ osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode);
+ localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+
+ osuConfineMode.ValueChanged += _ => updateConfineMode();
+ localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
+ }
+
+ private void updateConfineMode()
+ {
+ // confine mode is unavailable on some platforms
+ if (frameworkConfineMode.Disabled)
+ return;
+
+ switch (osuConfineMode.Value)
+ {
+ case OsuConfineMouseMode.Never:
+ frameworkConfineMode.Value = ConfineMouseMode.Never;
+ break;
+
+ case OsuConfineMouseMode.Fullscreen:
+ frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
+ break;
+
+ case OsuConfineMouseMode.DuringGameplay:
+ frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never;
+ break;
+
+ case OsuConfineMouseMode.Always:
+ frameworkConfineMode.Value = ConfineMouseMode.Always;
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs
new file mode 100644
index 0000000000..32b456395c
--- /dev/null
+++ b/osu.Game/Input/OsuConfineMouseMode.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+using osu.Framework.Input;
+
+namespace osu.Game.Input
+{
+ ///
+ /// Determines the situations in which the mouse cursor should be confined to the window.
+ /// Expands upon by providing the option to confine during gameplay.
+ ///
+ public enum OsuConfineMouseMode
+ {
+ ///
+ /// The mouse cursor will be free to move outside the game window.
+ ///
+ Never,
+
+ ///
+ /// The mouse cursor will be locked to the window bounds while in fullscreen mode.
+ ///
+ Fullscreen,
+
+ ///
+ /// The mouse cursor will be locked to the window bounds during gameplay,
+ /// but may otherwise move freely.
+ ///
+ [Description("During Gameplay")]
+ DuringGameplay,
+
+ ///
+ /// The mouse cursor will always be locked to the window bounds while the game has focus.
+ ///
+ Always
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 4a699dc82e..d315b213ab 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -95,6 +95,15 @@ namespace osu.Game
///
public readonly IBindable OverlayActivationMode = new Bindable();
+ ///
+ /// Whether the local user is currently interacting with the game in a way that should not be interrupted.
+ ///
+ ///
+ /// This is exclusively managed by . If other components are mutating this state, a more
+ /// resilient method should be used to ensure correct state.
+ ///
+ public Bindable LocalUserPlaying = new BindableBool();
+
protected OsuScreenStack ScreenStack;
protected BackButton BackButton;
@@ -577,7 +586,8 @@ namespace osu.Game
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
- idleTracker
+ idleTracker,
+ new ConfineMouseTracker()
});
ScreenStack.ScreenPushed += screenPushed;
@@ -947,6 +957,9 @@ namespace osu.Game
break;
}
+ // reset on screen change for sanity.
+ LocalUserPlaying.Value = false;
+
if (current is IOsuScreen currentOsuScreen)
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index a59a6b00b9..c213313559 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -4,9 +4,11 @@
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
+using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
@@ -21,6 +23,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
private SettingsButton checkForUpdatesButton;
+ [Resolved(CanBeNull = true)]
+ private NotificationOverlay notifications { get; set; }
+
[BackgroundDependencyLoader(true)]
private void load(Storage storage, OsuConfigManager config, OsuGame game)
{
@@ -38,7 +43,19 @@ namespace osu.Game.Overlays.Settings.Sections.General
Action = () =>
{
checkForUpdatesButton.Enabled.Value = false;
- Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true));
+ Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() =>
+ {
+ if (!t.Result)
+ {
+ notifications?.Post(new SimpleNotification
+ {
+ Text = $"You are running the latest release ({game.Version})",
+ Icon = FontAwesome.Solid.CheckCircle,
+ });
+ }
+
+ checkForUpdatesButton.Enabled.Value = true;
+ }));
}
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 5227e328ec..f0d51a0d37 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -6,9 +6,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
-using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Input;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -47,10 +47,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
LabelText = "Map absolute input to window",
Current = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow)
},
- new SettingsEnumDropdown
+ new SettingsEnumDropdown
{
LabelText = "Confine mouse cursor to window",
- Current = config.GetBindable(FrameworkSetting.ConfineMouseMode),
+ Current = osuConfig.GetBindable(OsuSetting.ConfineMouseMode)
},
new SettingsCheckbox
{
diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
index ac3b817840..58427f6945 100644
--- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
@@ -1,11 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
@@ -16,19 +16,16 @@ namespace osu.Game.Rulesets.Difficulty
protected readonly DifficultyAttributes Attributes;
protected readonly Ruleset Ruleset;
- protected readonly IBeatmap Beatmap;
protected readonly ScoreInfo Score;
protected double TimeRate { get; private set; } = 1;
- protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
+ protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
{
Ruleset = ruleset;
Score = score;
- Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
-
- Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods);
+ Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
ApplyMods(score.Mods);
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 8012b4d95c..1ef6c8c207 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
// apply any custom state overrides
ApplyCustomUpdateState?.Invoke(this, newState);
- if (newState == ArmedState.Hit)
+ if (!force && newState == ArmedState.Hit)
PlaySamples();
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 48d94d2b3f..8caadffd1d 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -160,7 +160,28 @@ namespace osu.Game.Rulesets
public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
- public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null;
+ ///
+ /// Optionally creates a to generate performance data from the provided score.
+ ///
+ /// Difficulty attributes for the beatmap related to the provided score.
+ /// The score to be processed.
+ /// A performance calculator instance for the provided score.
+ [CanBeNull]
+ public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
+
+ ///
+ /// Optionally creates a to generate performance data from the provided score.
+ ///
+ /// The beatmap to use as a source for generating .
+ /// The score to be processed.
+ /// A performance calculator instance for the provided score.
+ [CanBeNull]
+ public PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score)
+ {
+ var difficultyCalculator = CreateDifficultyCalculator(beatmap);
+ var difficultyAttributes = difficultyCalculator.Calculate(score.Mods);
+ return CreatePerformanceCalculator(difficultyAttributes, score);
+ }
public virtual HitObjectComposer CreateHitObjectComposer() => null;
diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
index 4121e1f7bb..c8982b819a 100644
--- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
+++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
@@ -113,7 +113,6 @@ namespace osu.Game.Screens.Edit.Timing
};
controlPoints = group.ControlPoints.GetBoundCopy();
- controlPoints.CollectionChanged += (_, __) => createChildren();
}
[Resolved]
@@ -125,6 +124,12 @@ namespace osu.Game.Screens.Edit.Timing
createChildren();
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ controlPoints.CollectionChanged += (_, __) => createChildren();
+ }
+
private void createChildren()
{
fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null);
diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs
index c77d48ef0a..d76b5e7406 100644
--- a/osu.Game/Screens/Edit/Timing/GroupSection.cs
+++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs
@@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Timing
foreach (var cp in currentGroupItems)
Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp);
- SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time);
+ // the control point might not necessarily exist yet, if currentGroupItems was empty.
+ SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true);
changeHandler?.EndChange();
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 175722c44e..74714e7e59 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
private readonly Bindable storyboardReplacesBackground = new Bindable();
+ protected readonly Bindable LocalUserPlaying = new Bindable();
+
public int RestartCount;
[Resolved]
@@ -155,8 +157,8 @@ namespace osu.Game.Screens.Play
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
}
- [BackgroundDependencyLoader]
- private void load(AudioManager audio, OsuConfigManager config)
+ [BackgroundDependencyLoader(true)]
+ private void load(AudioManager audio, OsuConfigManager config, OsuGame game)
{
Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray();
@@ -172,6 +174,9 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel);
+ if (game != null)
+ LocalUserPlaying.BindTo(game.LocalUserPlaying);
+
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
ScoreProcessor = ruleset.CreateScoreProcessor();
@@ -219,9 +224,9 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide();
}
- DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode());
- DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode());
- breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode());
+ DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
+ DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
+ breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@@ -353,14 +358,11 @@ namespace osu.Game.Screens.Play
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
}
- private void updateOverlayActivationMode()
+ private void updateGameplayState()
{
- bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value;
-
- if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays)
- OverlayActivationMode.Value = OverlayActivation.UserTriggered;
- else
- OverlayActivationMode.Value = OverlayActivation.Disabled;
+ bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
+ OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
+ LocalUserPlaying.Value = inGameplay;
}
private void updatePauseOnFocusLostState() =>
@@ -441,6 +443,10 @@ namespace osu.Game.Screens.Play
///
public void Restart()
{
+ // at the point of restarting the track should either already be paused or the volume should be zero.
+ // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader.
+ musicController.Stop();
+
sampleRestart?.Play();
RestartRequested?.Invoke();
@@ -657,7 +663,7 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType())
mod.ApplyToTrack(musicController.CurrentTrack);
- updateOverlayActivationMode();
+ updateGameplayState();
}
public override void OnSuspending(IScreen next)
diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs
index 48c6722bd9..b5fcb56c06 100644
--- a/osu.Game/Updater/SimpleUpdateManager.cs
+++ b/osu.Game/Updater/SimpleUpdateManager.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Updater
version = game.Version;
}
- protected override async Task PerformUpdateCheck()
+ protected override async Task PerformUpdateCheck()
{
try
{
@@ -53,12 +53,17 @@ namespace osu.Game.Updater
return true;
}
});
+
+ return true;
}
}
catch
{
// we shouldn't crash on a web failure. or any failure for the matter.
+ return true;
}
+
+ return false;
}
private string getBestUrl(GitHubRelease release)
diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs
index 61775a26b7..f772c6d282 100644
--- a/osu.Game/Updater/UpdateManager.cs
+++ b/osu.Game/Updater/UpdateManager.cs
@@ -57,25 +57,31 @@ namespace osu.Game.Updater
private readonly object updateTaskLock = new object();
- private Task updateCheckTask;
+ private Task updateCheckTask;
- public async Task CheckForUpdateAsync()
+ public async Task CheckForUpdateAsync()
{
if (!CanCheckForUpdate)
- return;
+ return false;
- Task waitTask;
+ Task waitTask;
lock (updateTaskLock)
waitTask = (updateCheckTask ??= PerformUpdateCheck());
- await waitTask;
+ bool hasUpdates = await waitTask;
lock (updateTaskLock)
updateCheckTask = null;
+
+ return hasUpdates;
}
- protected virtual Task PerformUpdateCheck() => Task.CompletedTask;
+ ///
+ /// Performs an asynchronous check for application updates.
+ ///
+ /// Whether any update is waiting. May return true if an error occured (there is potentially an update available).
+ protected virtual Task PerformUpdateCheck() => Task.FromResult(false);
private class UpdateCompleteNotification : SimpleNotification
{