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/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 09bfdc67d4..ad08f57c3a 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.1
+ netcoreapp2.2
WinExe
AnyCPU
true
@@ -28,8 +28,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/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/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/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/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 863ad3042f..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);
}
///
@@ -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/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
index de017baf35..2b0a49cb6c 100644
--- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs
+++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -15,20 +14,20 @@ using osuTK;
namespace osu.Game.Online.Chat
{
///
- /// An invisible drawable that brings multiple pieces together to form a consumable clickable link.
+ /// An invisible drawable that brings multiple pieces together to form a consumable clickable link.
///
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
{
///
/// Each word part of a chat link (split for word-wrap support).
///
- public List Parts;
+ public List Parts;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
- public DrawableLinkCompiler(IEnumerable parts)
+ public DrawableLinkCompiler(IEnumerable parts)
{
Parts = parts.ToList();
}
@@ -45,9 +44,9 @@ namespace osu.Game.Online.Chat
private class LinkHoverSounds : HoverClickSounds
{
- private readonly List parts;
+ private readonly List parts;
- public LinkHoverSounds(HoverSampleSet sampleSet, List parts)
+ public LinkHoverSounds(HoverSampleSet sampleSet, List parts)
: base(sampleSet)
{
this.parts = parts;
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 cd40d4793a..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 };
@@ -553,9 +555,9 @@ namespace osu.Game
try
{
- Logger.Log($"Loading {d}...", LoggingTarget.Debug);
+ Logger.Log($"Loading {d}...", level: LogLevel.Debug);
await LoadComponentAsync(d, add);
- Logger.Log($"Loaded {d}!", LoggingTarget.Debug);
+ Logger.Log($"Loaded {d}!", level: LogLevel.Debug);
}
catch (OperationCanceledException)
{
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/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 5d89e53081..40a395535d 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -7,7 +7,6 @@ using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
@@ -26,7 +25,7 @@ namespace osu.Game.Overlays.Music
private SpriteIcon handle;
private TextFlowContainer text;
- private IEnumerable titleSprites;
+ private IEnumerable titleSprites;
private ILocalisedBindableString titleBind;
private ILocalisedBindableString artistBind;
@@ -58,7 +57,7 @@ namespace osu.Game.Overlays.Music
selected = value;
FinishTransforms(true);
- foreach (SpriteText s in titleSprites)
+ foreach (Drawable s in titleSprites)
s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration);
}
}
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/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/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 611341eae0..fd2c94e814 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[]
{
@@ -212,7 +212,7 @@ namespace osu.Game.Screens.Play
failOverlay = new FailOverlay
{
OnRetry = Restart,
- OnQuit = Exit,
+ OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
{
@@ -226,7 +226,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);
@@ -251,8 +251,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/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 0e3dfcf284..cbe22d968a 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -1,111 +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 ExitFromBack()
- {
- if (modSelect.State == Visibility.Visible)
- {
- modSelect.Hide();
- return;
- }
-
- base.ExitFromBack();
- }
-
- 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)
@@ -115,38 +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 (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;
@@ -157,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;
}
}
@@ -168,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 66540c6900..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
@@ -194,21 +198,36 @@ namespace osu.Game.Screens.Select
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));
}
- protected virtual void ExitFromBack() => Exit();
-
[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)
@@ -223,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)
@@ -421,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);
@@ -438,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);
@@ -448,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;
@@ -456,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);
}
@@ -481,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)
@@ -491,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 85eabb0350..8ae644c06a 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -14,11 +14,11 @@
-
+
-
-
-
+
+
+