diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
index 344301d4a7..2735f4ceb3 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
@@ -1,17 +1,20 @@
-
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs
index 6c363422f7..e404ccd2b3 100644
--- a/osu.Desktop/Updater/SimpleUpdateManager.cs
+++ b/osu.Desktop/Updater/SimpleUpdateManager.cs
@@ -41,24 +41,32 @@ namespace osu.Desktop.Updater
private async void checkForUpdateAsync()
{
- var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest");
- await releases.PerformAsync();
-
- var latest = releases.ResponseObject;
-
- if (latest.TagName != version)
+ try
{
- notificationOverlay.Post(new SimpleNotification
+ var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest");
+
+ await releases.PerformAsync();
+
+ var latest = releases.ResponseObject;
+
+ if (latest.TagName != version)
{
- Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n"
- + "Click here to download the new version, which can be installed over the top of your existing installation",
- Icon = FontAwesome.fa_upload,
- Activated = () =>
+ notificationOverlay.Post(new SimpleNotification
{
- host.OpenUrlExternally(getBestUrl(latest));
- return true;
- }
- });
+ Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n"
+ + "Click here to download the new version, which can be installed over the top of your existing installation",
+ Icon = FontAwesome.fa_upload,
+ Activated = () =>
+ {
+ host.OpenUrlExternally(getBestUrl(latest));
+ return true;
+ }
+ });
+ }
+ }
+ catch
+ {
+ // we shouldn't crash on a web failure. or any failure for the matter.
}
}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index b253551da8..58f4e50f9e 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.1
+ netcoreapp2.2
WinExe
AnyCPU
true
@@ -29,8 +29,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index b76f591239..e875af5a30 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,12 +4,12 @@
-
+
WinExe
- netcoreapp2.1
+ netcoreapp2.2
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
index f38009263f..d89d987f95 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- protected override float HealthIncreaseFor(HitResult result)
+ protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
index 0df2305339..1fbf1db7f7 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- protected override float HealthIncreaseFor(HitResult result)
+ protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index 8a51867899..b20bc43886 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -22,29 +22,17 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- ///
- /// Retrieves the numeric health increase of a .
- ///
- /// The to find the numeric health increase for.
- /// The numeric health increase of .
- protected virtual float HealthIncreaseFor(HitResult result)
+ protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
- return 10.2f;
+ return 10.2;
}
}
- ///
- /// Retrieves the numeric health increase of a .
- ///
- /// The to find the numeric health increase for.
- /// The numeric health increase of .
- public float HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
-
///
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
index 8b77351027..fc933020d3 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- protected override float HealthIncreaseFor(HitResult result)
+ protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 403cedde8c..778d972b52 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -3,7 +3,6 @@
using System;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -40,8 +39,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
return;
}
- if (result.Judgement is CatchJudgement catchJudgement)
- Health.Value += Math.Max(catchJudgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
+ Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 98ad086c66..0c6fbfa7d3 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,12 +4,12 @@
-
+
WinExe
- netcoreapp2.1
+ netcoreapp2.2
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
index 063b626af1..ad0c04b4cc 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
@@ -20,11 +20,10 @@ namespace osu.Game.Rulesets.Mania.Objects
{ HitResult.Miss, (376, 346, 316) },
};
+ public override bool IsHitResultAllowed(HitResult result) => true;
+
public override void SetDifficulty(double difficulty)
{
- AllowsPerfect = true;
- AllowsOk = true;
-
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 6117812f45..35f137572d 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -4,12 +4,12 @@
-
+
WinExe
- netcoreapp2.1
+ netcoreapp2.2
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 3ba64398f3..0fc01deed6 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,12 +4,12 @@
-
+
WinExe
- netcoreapp2.1
+ netcoreapp2.2
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs
new file mode 100644
index 0000000000..8c88d6d073
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs
@@ -0,0 +1,24 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Judgements
+{
+ public class TaikoDrumRollJudgement : TaikoJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override double HealthIncreaseFor(HitResult result)
+ {
+ // Drum rolls can be ignored with no health penalty
+ switch (result)
+ {
+ case HitResult.Miss:
+ return 0;
+ default:
+ return base.HealthIncreaseFor(result);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs
index 446dd0d11b..e11bdf225f 100644
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs
@@ -13,10 +13,21 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{
switch (result)
{
- default:
- return 0;
case HitResult.Great:
return 200;
+ default:
+ return 0;
+ }
+ }
+
+ protected override double HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ return 0.15;
+ default:
+ return 0;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
deleted file mode 100644
index 81a1bd1344..0000000000
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Taiko.Judgements
-{
- public class TaikoIntermediateSwellJudgement : TaikoJudgement
- {
- public override HitResult MaxResult => HitResult.Great;
-
- public override bool AffectsCombo => false;
-
- ///
- /// Computes the numeric result value for the combo portion of the score.
- ///
- /// The result to compute the value for.
- /// The numeric result value.
- protected override int NumericResultFor(HitResult result) => 0;
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs
index 9b1f7a08b5..4f397cda09 100644
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs
@@ -10,21 +10,31 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{
public override HitResult MaxResult => HitResult.Great;
- ///
- /// Computes the numeric result value for the combo portion of the score.
- ///
- /// The result to compute the value for.
- /// The numeric result value.
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
- default:
- return 0;
case HitResult.Good:
return 100;
case HitResult.Great:
return 300;
+ default:
+ return 0;
+ }
+ }
+
+ protected override double HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Miss:
+ return -1.0;
+ case HitResult.Good:
+ return 1.1;
+ case HitResult.Great:
+ return 3.0;
+ default:
+ return 0;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs
index ccfdeb5b0e..81dfaf4cc3 100644
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs
@@ -1,10 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Game.Rulesets.Scoring;
+
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoStrongJudgement : TaikoJudgement
{
+ // MainObject already changes the HP
+ protected override double HealthIncreaseFor(HitResult result) => 0;
+
public override bool AffectsCombo => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs
new file mode 100644
index 0000000000..024e0e618f
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Judgements
+{
+ public class TaikoSwellJudgement : TaikoJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override double HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Miss:
+ return -0.65;
+ default:
+ return 0;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs
new file mode 100644
index 0000000000..448c16dad6
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Judgements
+{
+ public class TaikoSwellTickJudgement : TaikoJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result) => 0;
+
+ protected override double HealthIncreaseFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 6f7264e23b..d4f0360b40 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -173,13 +173,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered)
{
- if (timeOffset > second_hit_window)
+ if (timeOffset - MainObject.Result.TimeOffset > second_hit_window)
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
- if (Math.Abs(MainObject.Result.TimeOffset - timeOffset) < second_hit_window)
- ApplyResult(r => r.Type = HitResult.Great);
+ if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window)
+ ApplyResult(r => r.Type = MainObject.Result.Type);
}
public override bool OnPressed(TaikoAction action)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 36c468c6d6..0a73474cf3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -6,11 +6,11 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
- public class DrawableSwellTick : DrawableTaikoHitObject
+ public class DrawableSwellTick : DrawableTaikoHitObject
{
public override bool DisplayResult => false;
- public DrawableSwellTick(TaikoHitObject hitObject)
+ public DrawableSwellTick(SwellTick hitObject)
: base(hitObject)
{
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 405ea85f0d..89d0512e9c 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -5,6 +5,8 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
{
@@ -81,5 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
first = false;
}
}
+
+ public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index 702bf63bf5..68433429c6 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -3,6 +3,8 @@
using System;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
{
@@ -26,5 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
for (int i = 0; i < RequiredHits; i++)
AddNested(new SwellTick());
}
+
+ public override Judgement CreateJudgement() => new TaikoSwellJudgement();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
index 49eb6d2a15..38f77fa1e7 100644
--- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
@@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Taiko.Judgements;
+
namespace osu.Game.Rulesets.Taiko.Objects
{
public class SwellTick : TaikoHitObject
{
+ public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
index 289f084a45..9199e6f141 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
@@ -14,15 +14,26 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
{ HitResult.Great, (100, 70, 40) },
{ HitResult.Good, (240, 160, 100) },
- { HitResult.Meh, (270, 190, 140) },
- { HitResult.Miss, (400, 400, 400) },
+ { HitResult.Miss, (270, 190, 140) },
};
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Miss:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public override void SetDifficulty(double difficulty)
{
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index cf33141027..318efdbf3e 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
@@ -13,51 +12,24 @@ namespace osu.Game.Rulesets.Taiko.Scoring
internal class TaikoScoreProcessor : ScoreProcessor
{
///
- /// The HP awarded by a hit.
+ /// A value used for calculating .
///
- private const double hp_hit_great = 0.03;
-
- ///
- /// The HP awarded for a hit.
- ///
- private const double hp_hit_good = 0.011;
-
- ///
- /// The minimum HP deducted for a .
- /// This occurs when HP Drain = 0.
- ///
- private const double hp_miss_min = -0.0018;
-
- ///
- /// The median HP deducted for a .
- /// This occurs when HP Drain = 5.
- ///
- private const double hp_miss_mid = -0.0075;
-
- ///
- /// The maximum HP deducted for a .
- /// This occurs when HP Drain = 10.
- ///
- private const double hp_miss_max = -0.12;
-
- ///
- /// The HP awarded for a hit.
- ///
- /// hits award less HP as they're more spammable, although in hindsight
- /// this probably awards too little HP and is kept at this value for now for compatibility.
- ///
- ///
- private const double hp_hit_tick = 0.00000003;
+ private const double object_count_factor = 3;
///
/// Taiko fails at the end of the map if the player has not half-filled their HP bar.
///
protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
- private double hpIncreaseTick;
- private double hpIncreaseGreat;
- private double hpIncreaseGood;
- private double hpIncreaseMiss;
+ ///
+ /// HP multiplier for a successful .
+ ///
+ private double hpMultiplier;
+
+ ///
+ /// HP multiplier for a .
+ ///
+ private double hpMissMultiplier;
public TaikoScoreProcessor(RulesetContainer rulesetContainer)
: base(rulesetContainer)
@@ -68,38 +40,23 @@ namespace osu.Game.Rulesets.Taiko.Scoring
{
base.ApplyBeatmap(beatmap);
- double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
+ hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
- hpIncreaseTick = hp_hit_tick;
- hpIncreaseGreat = hpMultiplierNormal * hp_hit_great;
- hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
- hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}
protected override void ApplyResult(JudgementResult result)
{
base.ApplyResult(result);
- bool isTick = result.Judgement is TaikoDrumRollTickJudgement;
+ double hpIncrease = result.Judgement.HealthIncreaseFor(result);
- // Apply HP changes
- switch (result.Type)
- {
- case HitResult.Miss:
- // Missing ticks shouldn't drop HP
- if (!isTick)
- Health.Value += hpIncreaseMiss;
- break;
- case HitResult.Good:
- Health.Value += hpIncreaseGood;
- break;
- case HitResult.Great:
- if (isTick)
- Health.Value += hpIncreaseTick;
- else
- Health.Value += hpIncreaseGreat;
- break;
- }
+ if (result.Type == HitResult.Miss)
+ hpIncrease *= hpMissMultiplier;
+ else
+ hpIncrease *= hpMultiplier;
+
+ Health.Value += hpIncrease;
}
protected override void Reset(bool storeResults)
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 6d64b25906..f0211e1ead 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -295,6 +295,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
+
+ // The control point at the end time of the slider should be applied
+ Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
diff --git a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu
index 1e0e6f558e..8e7c504109 100644
--- a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu
+++ b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu
@@ -8,9 +8,12 @@ SampleSet: Normal
2638,-100,4,1,1,40,0,0
3107,-100,4,1,2,40,0,0
3576,-100,4,1,0,40,0,0
+18287,-100,4,2,11,80,0,1
+18595,-100,4,2,8,80,0,1
[HitObjects]
255,193,2170,1,0,0:0:0:0:
256,191,2638,5,0,0:0:0:0:
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:
+112,200,18493,6,0,L|104:248,1,35,8|0,0:0|0:0,0:0:0:0:
\ No newline at end of file
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 66363deb7c..7ee7acd539 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Scores.IO
var toImport = new ScoreInfo
{
- Statistics = new Dictionary
+ Statistics = new Dictionary
{
{ HitResult.Perfect, 100 },
{ HitResult.Miss, 50 }
diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
index 447337bef0..1127402adb 100644
--- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
@@ -4,15 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
-using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
@@ -74,50 +71,50 @@ namespace osu.Game.Tests.Visual
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString();
- AddStep("Add random private channel", addRandomUser);
+ AddStep("Add random private channel", addRandomPrivateChannel);
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);
- AddRepeatStep("Add 3 random private channels", addRandomUser, 3);
+ AddRepeatStep("Add 3 random private channels", addRandomPrivateChannel, 3);
AddAssert("There are four channels", () => channelTabControl.Items.Count() == 5);
AddStep("Add random public channel", () => addChannel(RNG.Next().ToString()));
- AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count())), 20);
- }
+ AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count() - 1)), 20);
- private List users;
+ Channel channelBefore = channelTabControl.Items.First();
+ AddStep("set first channel", () => channelTabControl.Current.Value = channelBefore);
- private void addRandomUser()
- {
- channelTabControl.AddChannel(new Channel
+ AddStep("select selector tab", () => channelTabControl.Current.Value = channelTabControl.Items.Last());
+ AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value);
+
+ AddAssert("check channel unchanged", () => channelBefore == channelTabControl.Current.Value);
+
+ AddStep("set second channel", () => channelTabControl.Current.Value = channelTabControl.Items.Skip(1).First());
+ AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value);
+
+ AddUntilStep(() =>
{
- Users =
- {
- users?.Count > 0
- ? users[RNG.Next(0, users.Count - 1)]
- : new User
- {
- Id = RNG.Next(),
- Username = "testuser" + RNG.Next(1000)
- }
- }
- });
+ var first = channelTabControl.Items.First();
+ if (first.Name == "+")
+ return true;
+
+ channelTabControl.RemoveChannel(first);
+ return false;
+ }, "remove all channels");
+
+ AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value);
}
- private void addChannel(string name)
- {
+ private void addRandomPrivateChannel() =>
+ channelTabControl.AddChannel(new Channel(new User
+ {
+ Id = RNG.Next(1000, 10000000),
+ Username = "Test User " + RNG.Next(1000)
+ }));
+
+ private void addChannel(string name) =>
channelTabControl.AddChannel(new Channel
{
Type = ChannelType.Public,
Name = name
});
- }
-
- [BackgroundDependencyLoader]
- private void load(IAPIProvider api)
- {
- GetUsersRequest req = new GetUsersRequest();
- req.Success += list => users = list.Select(e => e.User).ToList();
-
- api.Queue(req);
- }
}
}
diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
index 87235add37..d87a8d0056 100644
--- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
@@ -98,8 +98,11 @@ namespace osu.Game.Tests.Visual
[SetUp]
public virtual void SetUp()
{
- manager?.Delete(manager.GetAllUsableBeatmapSets());
- Child = songSelect = new TestSongSelect();
+ Schedule(() =>
+ {
+ manager?.Delete(manager.GetAllUsableBeatmapSets());
+ Child = songSelect = new TestSongSelect();
+ });
}
[Test]
diff --git a/osu.Game.Tests/Visual/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
new file mode 100644
index 0000000000..b4b9d465e5
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
@@ -0,0 +1,143 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Logging;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCasePollingComponent : OsuTestCase
+ {
+ private Container pollBox;
+ private TestPoller poller;
+
+ private const float safety_adjust = 1f;
+ private int count;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ count = 0;
+
+ Children = new Drawable[]
+ {
+ pollBox = new Container
+ {
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(0.4f),
+ Colour = Color4.LimeGreen,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Poll!",
+ }
+ }
+ }
+ };
+ });
+
+ [Test]
+ public void TestInstantPolling()
+ {
+ createPoller(true);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
+ checkCount(1);
+ checkCount(2);
+ checkCount(3);
+
+ AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
+ checkCount(4);
+ checkCount(4);
+ checkCount(4);
+
+ skip();
+
+ checkCount(5);
+ checkCount(5);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
+ checkCount(6);
+ checkCount(7);
+ }
+
+ [Test]
+ [Ignore("i have no idea how to fix the timing of this one")]
+ public void TestSlowPolling()
+ {
+ createPoller(false);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
+ checkCount(0);
+ skip();
+ checkCount(0);
+ skip();
+ skip();
+ checkCount(0);
+ skip();
+ skip();
+ checkCount(0);
+ }
+
+ private void skip() => AddStep("skip", () =>
+ {
+ // could be 4 or 5 at this point due to timing discrepancies (safety_adjust @ 0.2 * 5 ~= 1)
+ // easiest to just ignore the value at this point and move on.
+ });
+
+ private void checkCount(int checkValue)
+ {
+ Logger.Log($"value is {count}");
+ AddAssert($"count is {checkValue}", () => count == checkValue);
+ }
+
+ private void createPoller(bool instant) => AddStep("create poller", () =>
+ {
+ poller?.Expire();
+
+ Add(poller = instant ? new TestPoller() : new TestSlowPoller());
+ poller.OnPoll += () =>
+ {
+ pollBox.FadeOutFromOne(500);
+ count++;
+ };
+ });
+
+ protected override double TimePerAction => 500;
+
+ public class TestPoller : PollingComponent
+ {
+ public event Action OnPoll;
+
+ protected override Task Poll()
+ {
+ Schedule(() => OnPoll?.Invoke());
+ return base.Poll();
+ }
+ }
+
+ public class TestSlowPoller : TestPoller
+ {
+ protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs
index dfe1cdbfb0..6a20a808b6 100644
--- a/osu.Game.Tests/Visual/TestCaseResults.cs
+++ b/osu.Game.Tests/Visual/TestCaseResults.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
- Statistics = new Dictionary
+ Statistics = new Dictionary
{
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index c0f0695ff8..e6786dfd15 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -2,15 +2,15 @@
-
+
-
+
WinExe
- netcoreapp2.1
+ netcoreapp2.2
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 8728d776d0..c179821a7c 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -249,10 +249,13 @@ namespace osu.Game.Beatmaps
/// Retrieve a instance for the provided
///
/// The beatmap to lookup.
- /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap.
+ /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches
/// A instance correlating to the provided .
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null)
{
+ if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID)
+ return previous;
+
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 50767608af..77e92421e9 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -149,8 +149,10 @@ namespace osu.Game.Database
try
{
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}";
+
+ TModel import;
using (ArchiveReader reader = getReaderFrom(path))
- imported.Add(Import(reader));
+ imported.Add(import = Import(reader));
notification.Progress = (float)current / paths.Length;
@@ -160,7 +162,7 @@ namespace osu.Game.Database
// TODO: Add a check to prevent files from storage to be deleted.
try
{
- if (File.Exists(path))
+ if (import != null && File.Exists(path))
File.Delete(path);
}
catch (Exception e)
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 1dda257c95..cfbcf0326a 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Online.API
if (queue.Count == 0)
{
log.Add(@"Queueing a ping request");
- Queue(new ListChannelsRequest { Timeout = 5000 });
+ Queue(new GetUserRequest());
}
break;
@@ -173,7 +173,6 @@ namespace osu.Game.Online.API
req = queue.Dequeue();
}
- // TODO: handle failures better
handleRequest(req);
}
@@ -191,64 +190,30 @@ namespace osu.Game.Online.API
///
/// Handle a single API request.
+ /// Ensures all exceptions are caught and dealt with correctly.
///
/// The request.
- /// true if we should remove this request from the queue.
+ /// true if the request succeeded.
private bool handleRequest(APIRequest req)
{
try
{
- Logger.Log($@"Performing request {req}", LoggingTarget.Network);
req.Perform(this);
//we could still be in initialisation, at which point we don't want to say we're Online yet.
- if (IsLoggedIn)
- State = APIState.Online;
+ if (IsLoggedIn) State = APIState.Online;
failureCount = 0;
return true;
}
catch (WebException we)
{
- HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
- ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
-
- // special cases for un-typed but useful message responses.
- switch (we.Message)
- {
- case "Unauthorized":
- statusCode = HttpStatusCode.Unauthorized;
- break;
- }
-
- switch (statusCode)
- {
- case HttpStatusCode.Unauthorized:
- Logout(false);
- return true;
- case HttpStatusCode.RequestTimeout:
- failureCount++;
- log.Add($@"API failure count is now {failureCount}");
-
- if (failureCount < 3)
- //we might try again at an api level.
- return false;
-
- State = APIState.Failing;
- flushQueue();
- return true;
- }
-
- req.Fail(we);
- return true;
+ handleWebException(we);
+ return false;
}
catch (Exception e)
{
- if (e is TimeoutException)
- log.Add(@"API level timeout exception was hit");
-
- req.Fail(e);
- return true;
+ return false;
}
}
@@ -276,6 +241,45 @@ namespace osu.Game.Online.API
}
}
+ private bool handleWebException(WebException we)
+ {
+ HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
+ ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
+
+ // special cases for un-typed but useful message responses.
+ switch (we.Message)
+ {
+ case "Unauthorized":
+ case "Forbidden":
+ statusCode = HttpStatusCode.Unauthorized;
+ break;
+ }
+
+ switch (statusCode)
+ {
+ case HttpStatusCode.Unauthorized:
+ Logout(false);
+ return true;
+ case HttpStatusCode.RequestTimeout:
+ failureCount++;
+ log.Add($@"API failure count is now {failureCount}");
+
+ if (failureCount < 3)
+ //we might try again at an api level.
+ return false;
+
+ if (State == APIState.Online)
+ {
+ State = APIState.Failing;
+ flushQueue();
+ }
+
+ return true;
+ }
+
+ return true;
+ }
+
public bool IsLoggedIn => LocalUser.Value.Id > 1;
public void Queue(APIRequest request)
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index adbedb2aac..41f774e83c 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.IO.Network;
+using osu.Framework.Logging;
namespace osu.Game.Online.API
{
@@ -35,23 +36,12 @@ namespace osu.Game.Online.API
///
public abstract class APIRequest
{
- ///
- /// The maximum amount of time before this request will fail.
- ///
- public int Timeout = WebRequest.DEFAULT_TIMEOUT;
-
protected virtual string Target => string.Empty;
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
- private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds);
-
- public bool ExceededTimeout => remainingTime == 0;
-
- private DateTimeOffset? startTime;
-
protected APIAccess API;
protected WebRequest WebRequest;
@@ -75,27 +65,24 @@ namespace osu.Game.Online.API
{
API = api;
- if (checkAndProcessFailure())
+ if (checkAndScheduleFailure())
return;
- if (startTime == null)
- startTime = DateTimeOffset.UtcNow;
-
- if (remainingTime <= 0)
- throw new TimeoutException(@"API request timeout hit");
-
WebRequest = CreateWebRequest();
WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}");
- if (checkAndProcessFailure())
+ if (checkAndScheduleFailure())
return;
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
+ {
+ Logger.Log($@"Performing request {this}", LoggingTarget.Network);
WebRequest.Perform();
+ }
- if (checkAndProcessFailure())
+ if (checkAndScheduleFailure())
return;
api.Schedule(delegate { Success?.Invoke(); });
@@ -105,19 +92,21 @@ namespace osu.Game.Online.API
public void Fail(Exception e)
{
- cancelled = true;
+ if (cancelled) return;
+ cancelled = true;
WebRequest?.Abort();
+ Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
pendingFailure = () => Failure?.Invoke(e);
- checkAndProcessFailure();
+ checkAndScheduleFailure();
}
///
/// Checked for cancellation or error. Also queues up the Failed event if we can.
///
/// Whether we are in a failed or cancelled state.
- private bool checkAndProcessFailure()
+ private bool checkAndScheduleFailure()
{
if (API == null || pendingFailure == null) return cancelled;
diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs
index 838c4f95e4..b26bc751b9 100644
--- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Online.API.Requests.Responses
}
[JsonProperty(@"statistics")]
- private Dictionary jsonStats
+ private Dictionary jsonStats
{
set
{
diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs
index 9d3b7b5cc9..9dc357c403 100644
--- a/osu.Game/Online/Chat/Channel.cs
+++ b/osu.Game/Online/Chat/Channel.cs
@@ -88,6 +88,17 @@ namespace osu.Game.Online.Chat
{
}
+ ///
+ /// Create a private messaging channel with the specified user.
+ ///
+ /// The user to create the private conversation with.
+ public Channel(User user)
+ {
+ Type = ChannelType.PM;
+ Users.Add(user);
+ Name = user.Username;
+ }
+
///
/// Adds the argument message as a local echo. When this local echo is resolved will get called.
///
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 29f971078b..a63af0f7a3 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -4,11 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
-using osu.Framework.Graphics;
using osu.Framework.Logging;
-using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
@@ -18,7 +17,7 @@ namespace osu.Game.Online.Chat
///
/// Manages everything channel related
///
- public class ChannelManager : Component, IOnlineComponent
+ public class ChannelManager : PollingComponent
{
///
/// The channels the player joins on startup
@@ -49,11 +48,14 @@ namespace osu.Game.Online.Chat
public IBindableCollection AvailableChannels => availableChannels;
private IAPIProvider api;
- private ScheduledDelegate fetchMessagesScheduleder;
+
+ public readonly BindableBool HighPollRate = new BindableBool();
public ChannelManager()
{
CurrentChannel.ValueChanged += currentChannelChanged;
+
+ HighPollRate.BindValueChanged(high => TimeBetweenPolls = high ? 1000 : 6000, true);
}
///
@@ -79,7 +81,7 @@ namespace osu.Game.Online.Chat
throw new ArgumentNullException(nameof(user));
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id))
- ?? new Channel { Name = user.Username, Users = { user }, Type = ChannelType.PM };
+ ?? new Channel(user);
}
private void currentChannelChanged(Channel channel) => JoinChannel(channel);
@@ -360,73 +362,60 @@ namespace osu.Game.Online.Chat
}
}
- public void APIStateChanged(APIAccess api, APIState state)
- {
- switch (state)
- {
- case APIState.Online:
- fetchUpdates();
- break;
- default:
- fetchMessagesScheduleder?.Cancel();
- fetchMessagesScheduleder = null;
- break;
- }
- }
-
private long lastMessageId;
- private const int update_poll_interval = 1000;
private bool channelsInitialised;
- private void fetchUpdates()
+ protected override Task Poll()
{
- fetchMessagesScheduleder?.Cancel();
- fetchMessagesScheduleder = Scheduler.AddDelayed(() =>
+ if (!api.IsLoggedIn)
+ return base.Poll();
+
+ var fetchReq = new GetUpdatesRequest(lastMessageId);
+
+ var tcs = new TaskCompletionSource();
+
+ fetchReq.Success += updates =>
{
- var fetchReq = new GetUpdatesRequest(lastMessageId);
-
- fetchReq.Success += updates =>
+ if (updates?.Presence != null)
{
- if (updates?.Presence != null)
+ foreach (var channel in updates.Presence)
{
- foreach (var channel in updates.Presence)
- {
- // we received this from the server so should mark the channel already joined.
- JoinChannel(channel, true);
- }
-
- //todo: handle left channels
-
- handleChannelMessages(updates.Messages);
-
- foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
- JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
-
- lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
+ // we received this from the server so should mark the channel already joined.
+ JoinChannel(channel, true);
}
- if (!channelsInitialised)
- {
- channelsInitialised = true;
- // we want this to run after the first presence so we can see if the user is in any channels already.
- initializeChannels();
- }
+ //todo: handle left channels
- fetchUpdates();
- };
+ handleChannelMessages(updates.Messages);
- fetchReq.Failure += delegate { fetchUpdates(); };
+ foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
+ JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
- api.Queue(fetchReq);
- }, update_poll_interval);
+ lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
+ }
+
+ if (!channelsInitialised)
+ {
+ channelsInitialised = true;
+ // we want this to run after the first presence so we can see if the user is in any channels already.
+ initializeChannels();
+ }
+
+ tcs.SetResult(true);
+ };
+
+ fetchReq.Failure += _ => tcs.SetResult(false);
+
+ api.Queue(fetchReq);
+
+ return tcs.Task;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
this.api = api;
- api.Register(this);
}
}
diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs
new file mode 100644
index 0000000000..9d0bed7595
--- /dev/null
+++ b/osu.Game/Online/PollingComponent.cs
@@ -0,0 +1,118 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Threading.Tasks;
+using osu.Framework.Graphics;
+using osu.Framework.Threading;
+
+namespace osu.Game.Online
+{
+ ///
+ /// A component which requires a constant polling process.
+ ///
+ public abstract class PollingComponent : Component
+ {
+ private double? lastTimePolled;
+
+ private ScheduledDelegate scheduledPoll;
+
+ private bool pollingActive;
+
+ private double timeBetweenPolls;
+
+ ///
+ /// The time in milliseconds to wait between polls.
+ /// Setting to zero stops all polling.
+ ///
+ public double TimeBetweenPolls
+ {
+ get => timeBetweenPolls;
+ set
+ {
+ timeBetweenPolls = value;
+ scheduledPoll?.Cancel();
+ pollIfNecessary();
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// The initial time in milliseconds to wait between polls. Setting to zero stops al polling.
+ protected PollingComponent(double timeBetweenPolls = 0)
+ {
+ TimeBetweenPolls = timeBetweenPolls;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ pollIfNecessary();
+ }
+
+ private bool pollIfNecessary()
+ {
+ // we must be loaded so we have access to clock.
+ if (!IsLoaded) return false;
+
+ // there's already a poll process running.
+ if (pollingActive) return false;
+
+ // don't try polling if the time between polls hasn't been set.
+ if (timeBetweenPolls == 0) return false;
+
+ if (!lastTimePolled.HasValue)
+ {
+ doPoll();
+ return true;
+ }
+
+ if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
+ {
+ doPoll();
+ return true;
+ }
+
+ // not ennough time has passed since the last poll. we do want to schedule a poll to happen, though.
+ scheduleNextPoll();
+ return false;
+ }
+
+ private void doPoll()
+ {
+ scheduledPoll = null;
+ pollingActive = true;
+ Poll().ContinueWith(_ => pollComplete());
+ }
+
+ ///
+ /// Perform the polling in this method. Call when done.
+ ///
+ protected virtual Task Poll()
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Call when a poll operation has completed.
+ ///
+ private void pollComplete()
+ {
+ lastTimePolled = Time.Current;
+ pollingActive = false;
+
+ if (scheduledPoll == null)
+ scheduleNextPoll();
+ }
+
+ private void scheduleNextPoll()
+ {
+ scheduledPoll?.Cancel();
+
+ double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
+
+ scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 73ecbafb9e..31a00e68ac 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -418,6 +418,8 @@ namespace osu.Game
dependencies.Cache(notifications);
dependencies.Cache(dialogOverlay);
+ chatOverlay.StateChanged += state => channelManager.HighPollRate.Value = state == Visibility.Visible;
+
Add(externalLinkOpener = new ExternalLinkOpener());
var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications };
diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs
deleted file mode 100644
index 1f8c5d38b9..0000000000
--- a/osu.Game/Overlays/Chat/ChatTabControl.cs
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.UserInterface;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Online.Chat;
-using osuTK;
-using osuTK.Input;
-using osuTK.Graphics;
-using osu.Framework.Configuration;
-using System;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
-
-namespace osu.Game.Overlays.Chat
-{
- public class ChatTabControl : OsuTabControl
- {
- private const float shear_width = 10;
-
- public Action OnRequestLeave;
-
- public readonly Bindable ChannelSelectorActive = new Bindable();
-
- private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab;
-
- public ChatTabControl()
- {
- TabContainer.Margin = new MarginPadding { Left = 50 };
- TabContainer.Spacing = new Vector2(-shear_width, 0);
- TabContainer.Masking = false;
-
- AddInternal(new SpriteIcon
- {
- Icon = FontAwesome.fa_comments,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Size = new Vector2(20),
- Margin = new MarginPadding(10),
- });
-
- AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" }));
-
- ChannelSelectorActive.BindTo(selectorTab.Active);
- }
-
- protected override void AddTabItem(TabItem item, bool addToDropdown = true)
- {
- if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue)
- // performTabSort might've made selectorTab's position wonky, fix it
- TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
-
- base.AddTabItem(item, addToDropdown);
-
- if (SelectedTab == null)
- SelectTab(item);
- }
-
- protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
-
- protected override void SelectTab(TabItem tab)
- {
- if (tab is ChannelTabItem.ChannelSelectorTabItem)
- {
- tab.Active.Toggle();
- return;
- }
-
- selectorTab.Active.Value = false;
-
- base.SelectTab(tab);
- }
-
- private void tabCloseRequested(TabItem tab)
- {
- int totalTabs = TabContainer.Count - 1; // account for selectorTab
- int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs);
-
- if (tab == SelectedTab && totalTabs > 1)
- // Select the tab after tab-to-be-removed's index, or the tab before if current == last
- SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
- else if (totalTabs == 1 && !selectorTab.Active)
- // Open channel selection overlay if all channel tabs will be closed after removing this tab
- SelectTab(selectorTab);
-
- OnRequestLeave?.Invoke(tab.Value);
- }
-
- private class ChannelTabItem : TabItem
- {
- private Color4 backgroundInactive;
- private Color4 backgroundHover;
- private Color4 backgroundActive;
-
- public override bool IsRemovable => !Pinned;
-
- private readonly SpriteText text;
- private readonly SpriteText textBold;
- private readonly ClickableContainer closeButton;
- private readonly Box box;
- private readonly Box highlightBox;
- private readonly SpriteIcon icon;
-
- public Action OnRequestClose;
-
- private void updateState()
- {
- if (Active)
- fadeActive();
- else
- fadeInactive();
- }
-
- private const float transition_length = 400;
-
- private void fadeActive()
- {
- this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint);
-
- box.FadeColour(backgroundActive, transition_length, Easing.OutQuint);
- highlightBox.FadeIn(transition_length, Easing.OutQuint);
-
- text.FadeOut(transition_length, Easing.OutQuint);
- textBold.FadeIn(transition_length, Easing.OutQuint);
- }
-
- private void fadeInactive()
- {
- this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint);
-
- box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint);
- highlightBox.FadeOut(transition_length, Easing.OutQuint);
-
- text.FadeIn(transition_length, Easing.OutQuint);
- textBold.FadeOut(transition_length, Easing.OutQuint);
- }
-
- protected override bool OnMouseUp(MouseUpEvent e)
- {
- if (e.Button == MouseButton.Middle)
- {
- closeButton.Action();
- return true;
- }
-
- return false;
- }
-
- protected override bool OnHover(HoverEvent e)
- {
- if (IsRemovable)
- closeButton.FadeIn(200, Easing.OutQuint);
-
- if (!Active)
- box.FadeColour(backgroundHover, transition_length, Easing.OutQuint);
- return true;
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- closeButton.FadeOut(200, Easing.OutQuint);
- updateState();
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- backgroundActive = colours.ChatBlue;
- backgroundInactive = colours.Gray4;
- backgroundHover = colours.Gray7;
-
- highlightBox.Colour = colours.Yellow;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- updateState();
- }
-
- public ChannelTabItem(Channel value) : base(value)
- {
- Width = 150;
-
- RelativeSizeAxes = Axes.Y;
-
- Anchor = Anchor.BottomLeft;
- Origin = Anchor.BottomLeft;
-
- Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0);
-
- Masking = true;
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Radius = 10,
- Colour = Color4.Black.Opacity(0.2f),
- };
-
- Children = new Drawable[]
- {
- box = new Box
- {
- EdgeSmoothness = new Vector2(1, 0),
- RelativeSizeAxes = Axes.Both,
- },
- highlightBox = new Box
- {
- Width = 5,
- Alpha = 0,
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- EdgeSmoothness = new Vector2(1, 0),
- RelativeSizeAxes = Axes.Y,
- },
- new Container
- {
- Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0),
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- icon = new SpriteIcon
- {
- Icon = FontAwesome.fa_hashtag,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Colour = Color4.Black,
- X = -10,
- Alpha = 0.2f,
- Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
- },
- text = new OsuSpriteText
- {
- Margin = new MarginPadding(5),
- Origin = Anchor.CentreLeft,
- Anchor = Anchor.CentreLeft,
- Text = value.ToString(),
- TextSize = 18,
- },
- textBold = new OsuSpriteText
- {
- Alpha = 0,
- Margin = new MarginPadding(5),
- Origin = Anchor.CentreLeft,
- Anchor = Anchor.CentreLeft,
- Text = value.ToString(),
- Font = @"Exo2.0-Bold",
- TextSize = 18,
- },
- closeButton = new CloseButton
- {
- Alpha = 0,
- Margin = new MarginPadding { Right = 20 },
- Origin = Anchor.CentreRight,
- Anchor = Anchor.CentreRight,
- Action = delegate
- {
- if (IsRemovable) OnRequestClose?.Invoke(this);
- },
- },
- },
- },
- };
- }
-
- public class CloseButton : OsuClickableContainer
- {
- private readonly SpriteIcon icon;
-
- public CloseButton()
- {
- Size = new Vector2(20);
-
- Child = icon = new SpriteIcon
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(0.75f),
- Icon = FontAwesome.fa_close,
- RelativeSizeAxes = Axes.Both,
- };
- }
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
- return base.OnMouseDown(e);
- }
-
- protected override bool OnMouseUp(MouseUpEvent e)
- {
- icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
- return base.OnMouseUp(e);
- }
-
- protected override bool OnHover(HoverEvent e)
- {
- icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
- return base.OnHover(e);
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- icon.FadeColour(Color4.White, 200, Easing.OutQuint);
- base.OnHoverLost(e);
- }
- }
-
- public class ChannelSelectorTabItem : ChannelTabItem
- {
- public override bool IsRemovable => false;
-
- public override bool IsSwitchable => false;
-
- public ChannelSelectorTabItem(Channel value) : base(value)
- {
- Depth = float.MaxValue;
- Width = 45;
-
- icon.Alpha = 0;
-
- text.TextSize = 45;
- textBold.TextSize = 45;
- }
-
- [BackgroundDependencyLoader]
- private new void load(OsuColour colour)
- {
- backgroundInactive = colour.Gray2;
- backgroundActive = colour.Gray3;
- }
- }
-
- protected override void OnActivated() => updateState();
-
- protected override void OnDeactivated() => updateState();
- }
- }
-}
diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs
index ce5d961282..2418eda2b6 100644
--- a/osu.Game/Overlays/Chat/DrawableChannel.cs
+++ b/osu.Game/Overlays/Chat/DrawableChannel.cs
@@ -48,10 +48,6 @@ namespace osu.Game.Overlays.Chat
},
}
};
-
- Channel.NewMessagesArrived += newMessagesArrived;
- Channel.MessageRemoved += messageRemoved;
- Channel.PendingMessageResolved += pendingMessageResolved;
}
protected override void LoadComplete()
@@ -59,6 +55,11 @@ namespace osu.Game.Overlays.Chat
base.LoadComplete();
newMessagesArrived(Channel.Messages);
+
+ Channel.NewMessagesArrived += newMessagesArrived;
+ Channel.MessageRemoved += messageRemoved;
+ Channel.PendingMessageResolved += pendingMessageResolved;
+
scrollToEnd();
}
@@ -78,8 +79,6 @@ namespace osu.Game.Overlays.Chat
flow.AddRange(displayMessages.Select(m => new ChatLine(m)));
- if (!IsLoaded) return;
-
if (scroll.IsScrolledToEnd(10) || !flow.Children.Any() || newMessages.Any(m => m is LocalMessage))
scrollToEnd();
diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
index 3afac211f1..0cc0076903 100644
--- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
+++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
@@ -20,7 +20,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat.Selection
{
- public class ChannelSelectionOverlay : OsuFocusedOverlayContainer
+ public class ChannelSelectionOverlay : WaveOverlayContainer
{
public static readonly float WIDTH_PADDING = 170;
@@ -39,6 +39,11 @@ namespace osu.Game.Overlays.Chat.Selection
{
RelativeSizeAxes = Axes.X;
+ Waves.FirstWaveColour = OsuColour.FromHex("353535");
+ Waves.SecondWaveColour = OsuColour.FromHex("434343");
+ Waves.ThirdWaveColour = OsuColour.FromHex("515151");
+ Waves.FourthWaveColour = OsuColour.FromHex("595959");
+
Children = new Drawable[]
{
new Container
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
index 0b1721741a..b370d8f3c5 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
@@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Chat.Tabs
{
public override bool IsRemovable => false;
+ public override bool IsSwitchable => false;
+
public ChannelSelectorTabItem(Channel value) : base(value)
{
Depth = float.MaxValue;
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
index 79cb0a4d14..dc72c8053a 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
@@ -94,13 +94,12 @@ namespace osu.Game.Overlays.Chat.Tabs
{
if (tab is ChannelSelectorTabItem)
{
- tab.Active.Toggle();
+ tab.Active.Value = true;
return;
}
- selectorTab.Active.Value = false;
-
base.SelectTab(tab);
+ selectorTab.Active.Value = false;
}
private void tabCloseRequested(TabItem tab)
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index d026540fd9..8f9dcc054a 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -52,9 +52,9 @@ namespace osu.Game.Overlays
public Bindable ChatHeight { get; set; }
private readonly Container channelSelectionContainer;
- private readonly ChannelSelectionOverlay channelSelection;
+ private readonly ChannelSelectionOverlay channelSelectionOverlay;
- public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.ReceivePositionalInputAt(screenSpacePos);
+ public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || channelSelectionOverlay.State == Visibility.Visible && channelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos);
public ChatOverlay()
{
@@ -74,7 +74,7 @@ namespace osu.Game.Overlays
Masking = true,
Children = new[]
{
- channelSelection = new ChannelSelectionOverlay
+ channelSelectionOverlay = new ChannelSelectionOverlay
{
RelativeSizeAxes = Axes.Both,
},
@@ -161,9 +161,16 @@ namespace osu.Game.Overlays
};
channelTabControl.Current.ValueChanged += chat => channelManager.CurrentChannel.Value = chat;
- channelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden;
- channelSelection.StateChanged += state =>
+ channelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelectionOverlay.State = value ? Visibility.Visible : Visibility.Hidden;
+ channelSelectionOverlay.StateChanged += state =>
{
+ if (state == Visibility.Hidden && channelManager.CurrentChannel.Value == null)
+ {
+ channelSelectionOverlay.State = Visibility.Visible;
+ State = Visibility.Hidden;
+ return;
+ }
+
channelTabControl.ChannelSelectorActive.Value = state == Visibility.Visible;
if (state == Visibility.Visible)
@@ -176,8 +183,8 @@ namespace osu.Game.Overlays
textbox.HoldFocus = true;
};
- channelSelection.OnRequestJoin = channel => channelManager.JoinChannel(channel);
- channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel);
+ channelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel);
+ channelSelectionOverlay.OnRequestLeave = channel => channelManager.LeaveChannel(channel);
}
private void currentChannelChanged(Channel channel)
@@ -186,6 +193,7 @@ namespace osu.Game.Overlays
{
textbox.Current.Disabled = true;
currentChannelContainer.Clear(false);
+ channelSelectionOverlay.State = Visibility.Visible;
return;
}
@@ -239,7 +247,7 @@ namespace osu.Game.Overlays
double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
// If the channel selection screen is shown, mind its minimum height
- if (channelSelection.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height)
+ if (channelSelectionOverlay.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height)
targetChatHeight = 1f - channel_selection_min_height;
ChatHeight.Value = targetChatHeight;
@@ -305,7 +313,7 @@ namespace osu.Game.Overlays
channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged;
//for the case that channelmanager was faster at fetching the channels than our attachment to CollectionChanged.
- channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels);
+ channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
foreach (Channel channel in channelManager.JoinedChannels)
channelTabControl.AddChannel(channel);
}
@@ -326,7 +334,7 @@ namespace osu.Game.Overlays
}
private void availableChannelsChanged(IEnumerable channels)
- => channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels);
+ => channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
protected override void Dispose(bool isDisposing)
{
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
index f92990fc5d..1df07070a1 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
@@ -7,8 +7,8 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
using System;
+using System.Collections.Generic;
using System.Linq;
-using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
@@ -39,33 +39,34 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
foreach (var s in scores)
s.Ruleset = Rulesets.GetRuleset(s.RulesetID);
- ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0);
- ShowMoreLoading.Hide();
-
if (!scores.Any() && VisiblePages == 1)
{
+ ShowMoreButton.Hide();
+ ShowMoreLoading.Hide();
MissingText.Show();
return;
}
- MissingText.Hide();
+ IEnumerable drawableScores;
- foreach (APIScoreInfo score in scores)
+ switch (type)
{
- DrawableProfileScore drawableScore;
-
- switch (type)
- {
- default:
- drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null);
- break;
- case ScoreType.Recent:
- drawableScore = new DrawableTotalScore(score);
- break;
- }
-
- ItemsContainer.Add(drawableScore);
+ default:
+ drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null));
+ break;
+ case ScoreType.Recent:
+ drawableScores = scores.Select(score => new DrawableTotalScore(score));
+ break;
}
+
+ LoadComponentsAsync(drawableScores, s =>
+ {
+ MissingText.Hide();
+ ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0);
+ ShowMoreLoading.Hide();
+
+ ItemsContainer.AddRange(s);
+ });
});
Api.Queue(request);
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 23f35d5d3a..e259996b7f 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override FontAwesome Icon => FontAwesome.fa_paint_brush;
- private readonly Bindable dropdownBindable = new Bindable();
+ private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default };
private readonly Bindable configBindable = new Bindable();
private SkinManager skins;
diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs
index c679df5900..86a41a08ff 100644
--- a/osu.Game/Rulesets/Judgements/Judgement.cs
+++ b/osu.Game/Rulesets/Judgements/Judgement.cs
@@ -44,5 +44,19 @@ namespace osu.Game.Rulesets.Judgements
/// The to find the numeric score representation for.
/// The numeric score representation of .
public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type);
+
+ ///
+ /// Retrieves the numeric health increase of a .
+ ///
+ /// The to find the numeric health increase for.
+ /// The numeric health increase of .
+ protected virtual double HealthIncreaseFor(HitResult result) => 0;
+
+ ///
+ /// Retrieves the numeric health increase of a .
+ ///
+ /// The to find the numeric health increase for.
+ /// The numeric health increase of .
+ public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 8718269eed..e0728826df 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -147,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Plays all the hit sounds for this .
+ /// This is invoked automatically when this is hit.
///
public void PlaySamples() => Samples?.Play();
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 67a3db7a00..010fc450e0 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.Objects
///
public class HitObject
{
+ ///
+ /// A small adjustment to the start time of control points to account for rounding/precision errors.
+ ///
+ private const double control_point_leniency = 1;
+
///
/// The time at which the HitObject starts.
///
@@ -69,6 +74,9 @@ namespace osu.Game.Rulesets.Objects
{
ApplyDefaultsToSelf(controlPointInfo, difficulty);
+ // This is done here since ApplyDefaultsToSelf may be used to determine the end time
+ SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency);
+
nestedHitObjects.Clear();
CreateNestedHitObjects();
@@ -84,11 +92,7 @@ namespace osu.Game.Rulesets.Objects
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
- SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime);
- EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
-
- Kiai = effectPoint.KiaiMode;
- SampleControlPoint = samplePoint;
+ Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode;
if (HitWindows == null)
HitWindows = CreateHitWindows();
diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs
index 3717209860..40fb98a997 100644
--- a/osu.Game/Rulesets/Objects/HitWindows.cs
+++ b/osu.Game/Rulesets/Objects/HitWindows.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Objects
///
/// Hit window for a result.
- /// The user can only achieve receive this result if is true.
///
public double Perfect { get; protected set; }
@@ -38,7 +37,6 @@ namespace osu.Game.Rulesets.Objects
///
/// Hit window for an result.
- /// The user can only achieve this result if is true.
///
public double Ok { get; protected set; }
@@ -53,14 +51,36 @@ namespace osu.Game.Rulesets.Objects
public double Miss { get; protected set; }
///
- /// Whether it's possible to achieve a result.
+ /// Retrieves the with the largest hit window that produces a successful hit.
///
- public bool AllowsPerfect;
+ /// The lowest allowed successful .
+ protected HitResult LowestSuccessfulHitResult()
+ {
+ for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result)
+ {
+ if (IsHitResultAllowed(result))
+ return result;
+ }
+
+ return HitResult.None;
+ }
///
- /// Whether it's possible to achieve a result.
+ /// Check whether it is possible to achieve the provided .
///
- public bool AllowsOk;
+ /// The result type to check.
+ /// Whether the can be achieved.
+ public virtual bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Perfect:
+ case HitResult.Ok:
+ return false;
+ default:
+ return true;
+ }
+ }
///
/// Sets hit windows with values that correspond to a difficulty parameter.
@@ -85,18 +105,11 @@ namespace osu.Game.Rulesets.Objects
{
timeOffset = Math.Abs(timeOffset);
- if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect))
- return HitResult.Perfect;
- if (timeOffset <= HalfWindowFor(HitResult.Great))
- return HitResult.Great;
- if (timeOffset <= HalfWindowFor(HitResult.Good))
- return HitResult.Good;
- if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok))
- return HitResult.Ok;
- if (timeOffset <= HalfWindowFor(HitResult.Meh))
- return HitResult.Meh;
- if (timeOffset <= HalfWindowFor(HitResult.Miss))
- return HitResult.Miss;
+ for (var result = HitResult.Perfect; result >= HitResult.Miss; --result)
+ {
+ if (IsHitResultAllowed(result) && timeOffset <= HalfWindowFor(result))
+ return result;
+ }
return HitResult.None;
}
@@ -130,10 +143,10 @@ namespace osu.Game.Rulesets.Objects
///
/// Given a time offset, whether the can ever be hit in the future with a non- result.
- /// This happens if is less than what is required for a result.
+ /// This happens if is less than what is required for a result.
///
/// The time offset.
/// Whether the can be hit at any point in the future from this time offset.
- public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
+ public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(LowestSuccessfulHitResult());
}
}
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs
index 13fe021f95..3184f776a7 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs
@@ -116,12 +116,12 @@ namespace osu.Game.Scoring.Legacy
private void calculateAccuracy(ScoreInfo score)
{
- int countMiss = (int)score.Statistics[HitResult.Miss];
- int count50 = (int)score.Statistics[HitResult.Meh];
- int count100 = (int)score.Statistics[HitResult.Good];
- int count300 = (int)score.Statistics[HitResult.Great];
- int countGeki = (int)score.Statistics[HitResult.Perfect];
- int countKatu = (int)score.Statistics[HitResult.Ok];
+ int countMiss = score.Statistics[HitResult.Miss];
+ int count50 = score.Statistics[HitResult.Meh];
+ int count100 = score.Statistics[HitResult.Good];
+ int count300 = score.Statistics[HitResult.Great];
+ int countGeki = score.Statistics[HitResult.Perfect];
+ int countKatu = score.Statistics[HitResult.Ok];
switch (score.Ruleset.ID)
{
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index e6bab194b0..fb894e621e 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Scoring
public DateTimeOffset Date { get; set; }
[JsonIgnore]
- public Dictionary Statistics = new Dictionary();
+ public Dictionary Statistics = new Dictionary();
[Column("Statistics")]
public string StatisticsJson
@@ -118,7 +118,7 @@ namespace osu.Game.Scoring
return;
}
- Statistics = JsonConvert.DeserializeObject>(value);
+ Statistics = JsonConvert.DeserializeObject>(value);
}
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 98be0871a1..11cee98bdf 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play
Add(content = new Container
{
RelativeSizeAxes = Axes.Both,
-
+ AlwaysPresent = true, // The hud may be hidden but certain elements may need to still be updated
Children = new Drawable[]
{
ComboCounter = CreateComboCounter(),
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index bf44e9e636..19b49b099c 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play
{
Retries = RestartCount,
OnRetry = Restart,
- OnQuit = Exit,
+ OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
Children = new[]
{
@@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
failOverlay = new FailOverlay
{
OnRetry = Restart,
- OnQuit = Exit,
+ OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
{
@@ -225,7 +225,7 @@ namespace osu.Game.Screens.Play
}
};
- hudOverlay.HoldToQuit.Action = Exit;
+ hudOverlay.HoldToQuit.Action = performUserRequestedExit;
hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused);
@@ -250,8 +250,16 @@ namespace osu.Game.Screens.Play
mod.ApplyToClock(sourceClock);
}
+ private void performUserRequestedExit()
+ {
+ if (!IsCurrentScreen) return;
+ Exit();
+ }
+
public void Restart()
{
+ if (!IsCurrentScreen) return;
+
sampleRestart?.Play();
ValidForResume = false;
RestartRequested?.Invoke();
diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs
index 153d154d40..62103314e1 100644
--- a/osu.Game/Screens/Ranking/ResultsPageScore.cs
+++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs
@@ -196,9 +196,9 @@ namespace osu.Game.Screens.Ranking
private class DrawableScoreStatistic : Container
{
- private readonly KeyValuePair statistic;
+ private readonly KeyValuePair statistic;
- public DrawableScoreStatistic(KeyValuePair statistic)
+ public DrawableScoreStatistic(KeyValuePair statistic)
{
this.statistic = statistic;
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index b5d333aee4..cbe22d968a 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -1,100 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System.Collections.Generic;
using System.Linq;
-using osuTK.Input;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Configuration;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
-using osu.Game.Overlays;
-using osu.Game.Overlays.Mods;
-using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
-using osu.Game.Screens.Ranking;
-using osu.Game.Skinning;
+using osuTK.Input;
namespace osu.Game.Screens.Select
{
public class PlaySongSelect : SongSelect
{
- private OsuScreen player;
- private readonly ModSelectOverlay modSelect;
- protected readonly BeatmapDetailArea BeatmapDetails;
private bool removeAutoModOnResume;
+ private OsuScreen player;
- public PlaySongSelect()
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
{
- FooterPanels.Add(modSelect = new ModSelectOverlay
- {
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- });
-
- LeftContent.Add(BeatmapDetails = new BeatmapDetailArea
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = 10, Right = 5 },
- });
-
- BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
- }
-
- private SampleChannel sampleConfirm;
-
- [Cached]
- [Cached(Type = typeof(IBindable>))]
- private readonly Bindable> selectedMods = new Bindable>(new Mod[] { });
-
- [BackgroundDependencyLoader(true)]
- private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay, Bindable> selectedMods)
- {
- if (selectedMods != null) this.selectedMods.BindTo(selectedMods);
-
- sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
-
- Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue);
-
- BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
- BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, null, Key.Number2);
BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () =>
{
ValidForResume = false;
Edit();
}, Key.Number3);
-
- if (dialogOverlay != null)
- {
- Schedule(() =>
- {
- // if we have no beatmaps but osu-stable is found, let's prompt the user to import.
- if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
- dialogOverlay.Push(new ImportFromStablePopup(() =>
- {
- beatmaps.ImportFromStableAsync();
- skins.ImportFromStableAsync();
- }));
- });
- }
- }
-
- protected override void UpdateBeatmap(WorkingBeatmap beatmap)
- {
- beatmap.Mods.BindTo(selectedMods);
-
- base.UpdateBeatmap(beatmap);
-
- BeatmapDetails.Beatmap = beatmap;
-
- if (beatmap.Track != null)
- beatmap.Track.Looping = true;
}
protected override void OnResuming(Screen last)
@@ -104,44 +32,13 @@ namespace osu.Game.Screens.Select
if (removeAutoModOnResume)
{
var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType();
- modSelect.DeselectTypes(new[] { autoType }, true);
+ ModSelect.DeselectTypes(new[] { autoType }, true);
removeAutoModOnResume = false;
}
- BeatmapDetails.Leaderboard.RefreshScores();
-
- Beatmap.Value.Track.Looping = true;
-
base.OnResuming(last);
}
- protected override void OnSuspending(Screen next)
- {
- modSelect.Hide();
-
- base.OnSuspending(next);
- }
-
- protected override bool OnExiting(Screen next)
- {
- if (modSelect.State == Visibility.Visible)
- {
- modSelect.Hide();
- return true;
- }
-
- if (base.OnExiting(next))
- return true;
-
- if (Beatmap.Value.Track != null)
- Beatmap.Value.Track.Looping = false;
-
- selectedMods.UnbindAll();
- Beatmap.Value.Mods.Value = new Mod[] { };
-
- return false;
- }
-
protected override bool OnStart()
{
if (player != null) return false;
@@ -152,10 +49,10 @@ namespace osu.Game.Screens.Select
var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
var autoType = auto.GetType();
- var mods = selectedMods.Value;
+ var mods = SelectedMods.Value;
if (mods.All(m => m.GetType() != autoType))
{
- selectedMods.Value = mods.Append(auto);
+ SelectedMods.Value = mods.Append(auto);
removeAutoModOnResume = true;
}
}
@@ -163,7 +60,7 @@ namespace osu.Game.Screens.Select
Beatmap.Value.Track.Looping = false;
Beatmap.Disabled = true;
- sampleConfirm?.Play();
+ SampleConfirm?.Play();
LoadComponentAsync(player = new PlayerLoader(new Player()), l =>
{
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index f4af4f9068..71b63c8e5b 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
using System.Linq;
using osuTK;
using osuTK.Input;
@@ -21,12 +22,15 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
+using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Menu;
+using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select.Options;
+using osu.Game.Skinning;
namespace osu.Game.Screens.Select
{
@@ -60,29 +64,24 @@ namespace osu.Game.Screens.Select
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap();
- protected Container LeftContent;
-
protected readonly BeatmapCarousel Carousel;
private readonly BeatmapInfoWedge beatmapInfoWedge;
private DialogOverlay dialogOverlay;
private BeatmapManager beatmaps;
+ protected readonly ModSelectOverlay ModSelect;
+
+ protected SampleChannel SampleConfirm;
private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap;
+ protected readonly BeatmapDetailArea BeatmapDetails;
+
protected new readonly Bindable Ruleset = new Bindable();
- private DependencyContainer dependencies;
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- dependencies.CacheAs(this);
- dependencies.CacheAs(Ruleset);
- dependencies.CacheAs>(Ruleset);
-
- return dependencies;
- }
+ [Cached]
+ [Cached(Type = typeof(IBindable>))]
+ protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { });
protected SongSelect()
{
@@ -105,7 +104,7 @@ namespace osu.Game.Screens.Select
}
}
},
- LeftContent = new Container
+ new Container
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
@@ -117,6 +116,11 @@ namespace osu.Game.Screens.Select
Top = wedged_container_size.Y + left_area_padding,
Left = left_area_padding,
Right = left_area_padding * 2,
+ },
+ Child = BeatmapDetails = new BeatmapDetailArea
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = 10, Right = 5 },
}
},
new Container
@@ -191,22 +195,39 @@ namespace osu.Game.Screens.Select
});
Add(Footer = new Footer
{
- OnBack = Exit,
+ OnBack = ExitFromBack,
});
- FooterPanels.Add(BeatmapOptions = new BeatmapOptionsOverlay());
+ FooterPanels.AddRange(new Drawable[]
+ {
+ BeatmapOptions = new BeatmapOptionsOverlay(),
+ ModSelect = new ModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ }
+ });
}
+
+ BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
}
[BackgroundDependencyLoader(true)]
- private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours)
+ private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, Bindable> selectedMods)
{
+ if (selectedMods != null)
+ SelectedMods.BindTo(selectedMods);
+
if (Footer != null)
{
+ Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1);
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3);
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
+ BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
+ BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, null, Key.Number2);
}
if (this.beatmaps == null)
@@ -221,8 +242,46 @@ namespace osu.Game.Screens.Select
sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty");
sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand");
+ SampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
Carousel.LoadBeatmapSetsFromManager(this.beatmaps);
+
+ if (dialogOverlay != null)
+ {
+ Schedule(() =>
+ {
+ // if we have no beatmaps but osu-stable is found, let's prompt the user to import.
+ if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
+ dialogOverlay.Push(new ImportFromStablePopup(() =>
+ {
+ beatmaps.ImportFromStableAsync();
+ skins.ImportFromStableAsync();
+ }));
+ });
+ }
+ }
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs(this);
+ dependencies.CacheAs(Ruleset);
+ dependencies.CacheAs>(Ruleset);
+
+ return dependencies;
+ }
+
+ protected virtual void ExitFromBack()
+ {
+ if (ModSelect.State == Visibility.Visible)
+ {
+ ModSelect.Hide();
+ return;
+ }
+
+ Exit();
}
public void Edit(BeatmapInfo beatmap = null)
@@ -419,6 +478,10 @@ namespace osu.Game.Screens.Select
protected override void OnResuming(Screen last)
{
+ BeatmapDetails.Leaderboard.RefreshScores();
+
+ Beatmap.Value.Track.Looping = true;
+
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{
UpdateBeatmap(Beatmap.Value);
@@ -436,6 +499,8 @@ namespace osu.Game.Screens.Select
protected override void OnSuspending(Screen next)
{
+ ModSelect.Hide();
+
Content.ScaleTo(1.1f, 250, Easing.InSine);
Content.FadeOut(250);
@@ -446,6 +511,12 @@ namespace osu.Game.Screens.Select
protected override bool OnExiting(Screen next)
{
+ if (ModSelect.State == Visibility.Visible)
+ {
+ ModSelect.Hide();
+ return true;
+ }
+
FinaliseSelection(performStartAction: false);
beatmapInfoWedge.State = Visibility.Hidden;
@@ -454,6 +525,12 @@ namespace osu.Game.Screens.Select
FilterControl.Deactivate();
+ if (Beatmap.Value.Track != null)
+ Beatmap.Value.Track.Looping = false;
+
+ SelectedMods.UnbindAll();
+ Beatmap.Value.Mods.Value = new Mod[] { };
+
return base.OnExiting(next);
}
@@ -479,6 +556,8 @@ namespace osu.Game.Screens.Select
/// The working beatmap.
protected virtual void UpdateBeatmap(WorkingBeatmap beatmap)
{
+ beatmap.Mods.BindTo(SelectedMods);
+
Logger.Log($"working beatmap updated to {beatmap}");
if (Background is BackgroundScreenBeatmap backgroundModeBeatmap)
@@ -489,6 +568,11 @@ namespace osu.Game.Screens.Select
}
beatmapInfoWedge.Beatmap = beatmap;
+
+ BeatmapDetails.Beatmap = beatmap;
+
+ if (beatmap.Track != null)
+ beatmap.Track.Looping = true;
}
private void ensurePlayingSelected(bool preview = false)
diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs
index 25d9442e6f..d7d2737d35 100644
--- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs
+++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs
@@ -16,8 +16,8 @@ namespace osu.Game.Skinning
{
public event Action SourceChanged;
- private Bindable beatmapSkins = new Bindable();
- private Bindable beatmapHitsounds = new Bindable();
+ private readonly Bindable beatmapSkins = new Bindable();
+ private readonly Bindable beatmapHitsounds = new Bindable();
public Drawable GetDrawableComponent(string componentName)
{
@@ -84,11 +84,8 @@ namespace osu.Game.Skinning
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
- beatmapSkins = config.GetBindable(OsuSetting.BeatmapSkins);
- beatmapSkins.BindValueChanged(_ => onSourceChanged());
-
- beatmapHitsounds = config.GetBindable(OsuSetting.BeatmapHitsounds);
- beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
+ config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
+ config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds);
}
protected override void LoadComplete()
@@ -97,6 +94,9 @@ namespace osu.Game.Skinning
if (fallbackSource != null)
fallbackSource.SourceChanged += onSourceChanged;
+
+ beatmapSkins.BindValueChanged(_ => onSourceChanged());
+ beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 3c3550189b..87b8b036df 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -15,10 +15,10 @@
-
+
-
-
+
+