diff --git a/.vscode/launch.json b/.vscode/launch.json
index df5b11f63a..32c82685c0 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -22,7 +22,7 @@
},
"type": "mono",
"request": "launch",
- "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe",
+ "program": "${workspaceRoot}/osu.Game.Tests/bin/Release/net471/osu.Game.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
@@ -66,7 +66,7 @@
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Debug, dotnet)",
+ "preLaunchTask": "Build tests (Debug, dotnet)",
"env": {},
"console": "internalConsole"
},
@@ -76,10 +76,10 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Release, dotnet)",
+ "preLaunchTask": "Build tests (Release, dotnet)",
"env": {},
"console": "internalConsole"
},
@@ -92,7 +92,7 @@
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll",
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Debug, dotnet)",
+ "preLaunchTask": "Build osu! (Debug, dotnet)",
"env": {},
"console": "internalConsole"
},
@@ -105,7 +105,7 @@
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.0/osu!.dll",
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Release, dotnet)",
+ "preLaunchTask": "Build osu! (Release, dotnet)",
"env": {},
"console": "internalConsole"
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 7144a584f3..0908ff6108 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -31,7 +31,7 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Build (Debug, dotnet)",
+ "label": "Build osu! (Debug, dotnet)",
"type": "shell",
"command": "dotnet",
"args": [
@@ -47,7 +47,7 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Build (Release, dotnet)",
+ "label": "Build osu! (Release, dotnet)",
"type": "shell",
"command": "dotnet",
"args": [
@@ -63,6 +63,39 @@
"group": "build",
"problemMatcher": "$msCompile"
},
+ {
+ "label": "Build tests (Debug, dotnet)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tests",
+ "/p:TargetFramework=netcoreapp2.0",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Build tests (Release, dotnet)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tests",
+ "/p:TargetFramework=netcoreapp2.0",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
{
"label": "Restore (net471)",
"type": "shell",
diff --git a/appveyor.yml b/appveyor.yml
index 15484e4c68..69bc762f4c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -11,7 +11,7 @@ install:
- cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.4/CodeFileSanity.exe
+ - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.5/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- cmd: nuget restore -verbosity quiet
diff --git a/osu-framework b/osu-framework
index 0773d895d9..aebfa5bc5c 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed
+Subproject commit aebfa5bc5c634c1fd0c103e0c17518e5111a67c7
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 3d64cab84e..af027da2fc 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,4 +1,4 @@
-
+
net471;netcoreapp2.0
@@ -20,6 +20,8 @@
osu.Desktop.Program
+
+
@@ -30,10 +32,14 @@
-
+
-
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index bd0cc209b6..5b34e46247 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -5,8 +5,6 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class CatchBeatmapConversionTest : BeatmapConversionTest
+ internal class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
@@ -47,10 +45,10 @@ namespace osu.Game.Rulesets.Catch.Tests
}
}
- protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
}
- public struct ConvertValue : IEquatable
+ internal struct ConvertValue : IEquatable
{
///
/// A sane value to account for osu!stable using ints everwhere.
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
index bce20520d3..097750d7e0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
index d13a6bb860..b5cf0e3d1d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
index 2b58fcc93c..8a90b48180 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
index f239290ed4..5119260c53 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft
+ Origin = Anchor.TopLeft
},
};
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
index 275752523d..e77dd76353 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableCatchHitObject),
typeof(DrawableFruit),
typeof(DrawableDroplet),
+ typeof(BananaShower),
typeof(Pulp),
};
@@ -53,12 +54,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
- var fruit = new Fruit
- {
- StartTime = 1000000000000,
- IndexInBeatmap = index,
- Scale = 1.5f,
- };
+ Fruit fruit = index == 5
+ ? new BananaShower.Banana
+ {
+ StartTime = 1000000000000,
+ IndexInBeatmap = index,
+ Scale = 1.5f,
+ }
+ : new Fruit
+ {
+ StartTime = 1000000000000,
+ IndexInBeatmap = index,
+ Scale = 1.5f,
+ };
return new DrawableFruit(fruit)
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
index e7f936ca2a..896582bf0a 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
new file mode 100644
index 0000000000..5b4af6ea8a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -0,0 +1,43 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Catch.Objects;
+
+namespace osu.Game.Rulesets.Catch.Beatmaps
+{
+ public class CatchBeatmap : Beatmap
+ {
+ public override IEnumerable GetStatistics()
+ {
+ int fruits = HitObjects.Count(s => s is Fruit);
+ int juiceStreams = HitObjects.Count(s => s is JuiceStream);
+ int bananaShowers = HitObjects.Count(s => s is BananaShower);
+
+ return new[]
+ {
+ new BeatmapStatistic
+ {
+ Name = @"Fruit Count",
+ Content = fruits.ToString(),
+ Icon = FontAwesome.fa_circle_o
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Juice Stream Count",
+ Content = juiceStreams.ToString(),
+ Icon = FontAwesome.fa_circle
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Banana Shower Count",
+ Content = bananaShowers.ToString(),
+ Icon = FontAwesome.fa_circle
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 34e5f425fd..ad500606ed 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -13,9 +13,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapConverter : BeatmapConverter
{
+ public CatchBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
- protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
var curveData = obj as IHasCurve;
var positionData = obj as IHasXPosition;
@@ -23,7 +28,20 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var endTime = obj as IHasEndTime;
if (positionData == null)
+ {
+ if (endTime != null)
+ {
+ yield return new BananaShower
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ Duration = endTime.Duration,
+ NewCombo = comboData?.NewCombo ?? false
+ };
+ }
+
yield break;
+ }
if (curveData != null)
{
@@ -43,19 +61,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
yield break;
}
- if (endTime != null)
- {
- yield return new BananaShower
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- Duration = endTime.Duration,
- NewCombo = comboData?.NewCombo ?? false
- };
-
- yield break;
- }
-
yield return new Fruit
{
StartTime = obj.StartTime,
@@ -64,5 +69,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
X = positionData.X / CatchPlayfield.BASE_WIDTH
};
}
+
+ protected override Beatmap CreateBeatmap() => new CatchBeatmap();
}
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index dfd10e0df7..e16f5fcb60 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -12,16 +12,21 @@ using OpenTK;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
- public class CatchBeatmapProcessor : BeatmapProcessor
+ public class CatchBeatmapProcessor : BeatmapProcessor
{
- public override void PostProcess(Beatmap beatmap)
+ public CatchBeatmapProcessor(IBeatmap beatmap)
+ : base(beatmap)
{
- initialiseHyperDash(beatmap.HitObjects);
+ }
- base.PostProcess(beatmap);
+ public override void PostProcess()
+ {
+ initialiseHyperDash((List)Beatmap.HitObjects);
+
+ base.PostProcess();
int index = 0;
- foreach (var obj in beatmap.HitObjects)
+ foreach (var obj in Beatmap.HitObjects.OfType())
obj.IndexInBeatmap = index++;
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index cfe0fc5cec..2325a8cad9 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -13,12 +13,17 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset);
+ public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+ public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
@@ -138,7 +143,7 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
+ public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
public override int? LegacyID => 2;
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
similarity index 55%
rename from osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
rename to osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 876b394da0..f8351b7519 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,21 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
-namespace osu.Game.Rulesets.Catch
+namespace osu.Game.Rulesets.Catch.Difficulty
{
- public class CatchDifficultyCalculator : DifficultyCalculator
+ public class CatchDifficultyCalculator : DifficultyCalculator
{
- public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
+ public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
{
}
public override double Calculate(Dictionary categoryDifficulty = null) => 0;
-
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index df7578799f..8e19c0614a 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -6,10 +6,11 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using System;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModHardRock : ModHardRock, IApplicableToHitObject
+ public class CatchModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1.12;
public override bool Ranked => true;
@@ -17,9 +18,11 @@ namespace osu.Game.Rulesets.Catch.Mods
private float lastStartX;
private int lastStartTime;
- public void ApplyToHitObject(CatchHitObject hitObject)
+ public void ApplyToHitObject(HitObject hitObject)
{
- float position = hitObject.X;
+ var catchObject = (CatchHitObject)hitObject;
+
+ float position = catchObject.X;
int startTime = (int)hitObject.StartTime;
if (lastStartX == 0)
@@ -60,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Mods
position += rand;
}
- hitObject.X = position;
+ catchObject.X = position;
return;
}
@@ -79,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Mods
}
}
- hitObject.X = position;
+ catchObject.X = position;
lastStartX = position;
lastStartTime = startTime;
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 95ffd41518..548813fbd2 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Objects
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
+
+ protected override HitWindows CreateHitWindows() => null;
}
public enum FruitVisualRepresentation
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 41792b10a4..4603148114 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -18,12 +18,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
private Circle border;
+ private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust;
+
+ ///
+ /// Because we're adding a border around the fruit, we need to scale down some.
+ ///
+ private const float radius_adjust = 1.1f;
+
public DrawableFruit(Fruit h)
: base(h)
{
Origin = Anchor.Centre;
- Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
+ Size = new Vector2(drawable_radius);
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@@ -44,14 +51,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow,
- Radius = 4,
+ Radius = 4 * radius_adjust,
Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
},
- Size = new Vector2(Height * 1.5f),
+ Size = new Vector2(Height),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BorderColour = Color4.White,
- BorderThickness = 4f,
+ BorderThickness = 3f * radius_adjust,
Children = new Framework.Graphics.Drawable[]
{
new Box
@@ -82,8 +89,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
{
- const float large_pulp_3 = 13f;
- const float distance_from_centre_3 = 0.23f;
+ const float large_pulp_3 = 8f * radius_adjust;
+ const float distance_from_centre_3 = 0.15f;
const float large_pulp_4 = large_pulp_3 * 0.925f;
const float distance_from_centre_4 = distance_from_centre_3 / 0.925f;
@@ -106,11 +113,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = 0.05f,
+ Y = -0.34f,
},
new Pulp
{
@@ -146,11 +151,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = 0.1f,
+ Y = -0.3f,
},
new Pulp
{
@@ -186,11 +189,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = -0.1f,
+ Y = -0.33f,
},
new Pulp
{
@@ -220,10 +221,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
+ Y = -0.25f,
},
new Pulp
{
@@ -253,16 +253,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = -0.15f
+ Y = -0.3f
},
new Pulp
{
AccentColour = AccentColour,
- Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3),
+ Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
+ Y = 0.05f,
},
}
};
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index d17a72a165..250dc8c7f1 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -29,14 +29,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
set
{
accentColour = value;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 8,
- Colour = accentColour.Darken(0.2f).Opacity(0.75f)
- };
+ if (IsLoaded) updateAccentColour();
}
}
+
+ private void updateAccentColour()
+ {
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = Size.X / 2,
+ Colour = accentColour.Darken(0.2f).Opacity(0.75f)
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateAccentColour();
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index d63d1bd331..d5c5eb844a 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Replays
Dashing = dashing;
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
{
Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 022a8a8b43..52763e09af 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -4,7 +4,6 @@
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
-using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
@@ -20,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchRulesetContainer : ScrollingRulesetContainer
{
- public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
@@ -29,14 +28,12 @@ namespace osu.Game.Rulesets.Catch.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
- protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
-
- protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter();
-
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
+ protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate
+
protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h)
{
switch (h)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 181536a91e..d8c7b5130d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
- public const float CATCHER_SIZE = 172;
+ public const float CATCHER_SIZE = 84;
protected readonly Catcher MovableCatcher;
@@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler
{
- private Texture texture;
-
private Container caughtFruit;
public Container ExplodingFruitTarget;
@@ -121,10 +119,8 @@ namespace osu.Game.Rulesets.Catch.UI
}
[BackgroundDependencyLoader]
- private void load(TextureStore textures)
+ private void load()
{
- texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
-
Children = new Drawable[]
{
caughtFruit = new Container
@@ -196,13 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
- private Sprite createCatcherSprite() => new Sprite
- {
- Size = new Vector2(CATCHER_SIZE),
- FillMode = FillMode.Fill,
- Texture = texture,
- OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
- };
+ private Sprite createCatcherSprite() => new CatcherSprite();
///
/// Add a caught fruit to the catcher's stack.
@@ -411,6 +401,23 @@ namespace osu.Game.Rulesets.Catch.UI
f.Expire();
}
}
+
+ private class CatcherSprite : Sprite
+ {
+ public CatcherSprite()
+ {
+ Size = new Vector2(CATCHER_SIZE);
+
+ // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
+ OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
+ }
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 81c537e53c..5ae899f6d6 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -5,8 +5,6 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -14,17 +12,14 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class ManiaBeatmapConversionTest : BeatmapConversionTest
+ internal class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
- private bool isForCurrentRuleset;
-
[NonParallelizable]
- [TestCase("basic", false)]
- public void Test(string name, bool isForCurrentRuleset)
+ [TestCase("basic")]
+ public new void Test(string name)
{
- this.isForCurrentRuleset = isForCurrentRuleset;
base.Test(name);
}
@@ -38,10 +33,10 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
- protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
- public struct ConvertValue : IEquatable
+ internal struct ConvertValue : IEquatable
{
///
/// A sane value to account for osu!stable using ints everwhere.
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
index 281c2789af..a4109722d4 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Tests.Visual;
@@ -17,6 +19,14 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public TestCaseManiaHitObjects()
{
+ Note note1 = new Note();
+ Note note2 = new Note();
+ HoldNote holdNote = new HoldNote { StartTime = 1000 };
+
+ note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -43,14 +53,14 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
- new DrawableNote(new Note(), ManiaAction.Key1)
+ new DrawableNote(note1, ManiaAction.Key1)
{
Y = 5000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
},
- new DrawableNote(new Note(), ManiaAction.Key1)
+ new DrawableNote(note2, ManiaAction.Key1)
{
Y = 6000,
LifetimeStart = double.MinValue,
@@ -77,13 +87,13 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
- new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1)
+ new DrawableHoldNote(holdNote, ManiaAction.Key1)
{
Y = 5000,
Height = 1000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red
+ AccentColour = Color4.Red,
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
index 053f478027..dff2b2d56a 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
@@ -8,6 +8,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
@@ -83,13 +85,16 @@ namespace osu.Game.Rulesets.Mania.Tests
int col = rng.Next(0, 4);
- var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1)
+ var note = new Note { Column = col };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ var drawableNote = new DrawableNote(note, ManiaAction.Key1)
{
AccentColour = playfield.Columns.ElementAt(col).AccentColour
};
- playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
- playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
+ playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
+ playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
});
}
@@ -162,32 +167,24 @@ namespace osu.Game.Rulesets.Mania.Tests
for (double t = start_time; t <= start_time + duration; t += 100)
{
- playfield.Add(new DrawableNote(new Note
- {
- StartTime = t,
- Column = 0
- }, ManiaAction.Key1));
+ var note1 = new Note { StartTime = t, Column = 0 };
+ var note2 = new Note { StartTime = t, Column = 3 };
- playfield.Add(new DrawableNote(new Note
- {
- StartTime = t,
- Column = 3
- }, ManiaAction.Key4));
+ note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
+ playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
}
- playfield.Add(new DrawableHoldNote(new HoldNote
- {
- StartTime = start_time,
- Duration = duration,
- Column = 1
- }, ManiaAction.Key2));
+ var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
+ var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
- playfield.Add(new DrawableHoldNote(new HoldNote
- {
- StartTime = start_time,
- Duration = duration,
- Column = 2
- }, ManiaAction.Key3));
+ holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
+ playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index 6af3719f83..ad5f8e447d 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
@@ -29,5 +30,27 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
Stages.Add(defaultStage);
}
+
+ public override IEnumerable GetStatistics()
+ {
+ int notes = HitObjects.Count(s => s is Note);
+ int holdnotes = HitObjects.Count(s => s is HoldNote);
+
+ return new[]
+ {
+ new BeatmapStatistic
+ {
+ Name = @"Note Count",
+ Content = notes.ToString(),
+ Icon = FontAwesome.fa_circle_o
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Hold Note Count",
+ Content = holdnotes.ToString(),
+ Icon = FontAwesome.fa_circle
+ },
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 60b92cb7b3..19fef9eb54 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -33,18 +33,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private ManiaBeatmap beatmap;
- public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original)
+ public ManiaBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
{
- IsForCurrentRuleset = isForCurrentRuleset;
+ IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
- var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize);
- var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+ var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
- if (isForCurrentRuleset)
+ if (IsForCurrentRuleset)
TargetColumns = (int)Math.Max(1, roundedCircleSize);
else
{
- float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count;
+ float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count();
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
}
- protected override Beatmap ConvertBeatmap(Beatmap original)
+ protected override Beatmap ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
@@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
- protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
var maniaOriginal = original as ManiaHitObject;
if (maniaOriginal != null)
@@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The original hit object.
/// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.
/// The hit objects generated.
- private IEnumerable generateSpecific(HitObject original, Beatmap originalBeatmap)
+ private IEnumerable generateSpecific(HitObject original, IBeatmap originalBeatmap)
{
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
@@ -128,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The original hit object.
/// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.
/// The hit objects generated.
- private IEnumerable generateConverted(HitObject original, Beatmap originalBeatmap)
+ private IEnumerable generateConverted(HitObject original, IBeatmap originalBeatmap)
{
var endTimeData = original as IHasEndTime;
var distanceData = original as IHasDistance;
@@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{
- public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+ public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 3b5c028bfd..afa9bdbbd7 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType;
- public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+ public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
convertType = PatternType.None;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 743e230cb2..3f34afee85 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
private readonly double endTime;
- public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap)
+ public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
var endtimeData = HitObject as IHasEndTime;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 652c92dd78..cec3e18ad6 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType;
- public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap)
+ public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 02306846a3..930597c1ad 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// The beatmap which is being converted from.
///
- protected readonly Beatmap OriginalBeatmap;
+ protected readonly IBeatmap OriginalBeatmap;
- protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+ protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern)
{
if (random == null) throw new ArgumentNullException(nameof(random));
@@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime /= 1000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
+ conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
similarity index 89%
rename from osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
rename to osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 5eea346836..2517839355 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -1,16 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
-using System;
-using System.Collections.Generic;
-namespace osu.Game.Rulesets.Mania
+namespace osu.Game.Rulesets.Mania.Difficulty
{
- internal class ManiaDifficultyCalculator : DifficultyCalculator
+ internal class ManiaDifficultyCalculator : DifficultyCalculator
{
private const double star_scaling_factor = 0.018;
@@ -31,12 +33,12 @@ namespace osu.Game.Rulesets.Mania
///
private readonly List difficultyHitObjects = new List();
- public ManiaDifficultyCalculator(Beatmap beatmap)
+ public ManiaDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{
}
- public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods)
+ public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
: base(beatmap, mods)
{
}
@@ -48,18 +50,17 @@ namespace osu.Game.Rulesets.Mania
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
- foreach (var hitObject in Beatmap.HitObjects)
- difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount));
-
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
- difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
+ // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
+ difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
if (!calculateStrainValues())
return 0;
double starRating = calculateDifficulty() * star_scaling_factor;
- categoryDifficulty?.Add("Strain", starRating);
+ if (categoryDifficulty != null)
+ categoryDifficulty["Strain"] = starRating;
return starRating;
}
@@ -140,7 +141,5 @@ namespace osu.Game.Rulesets.Mania
return difficulty;
}
-
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
new file mode 100644
index 0000000000..93652f7610
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -0,0 +1,127 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+ public class ManiaPerformanceCalculator : PerformanceCalculator
+ {
+ private Mod[] mods;
+
+ // Score after being scaled by non-difficulty-increasing mods
+ private double scaledScore;
+
+ private int countPerfect;
+ private int countGreat;
+ private int countGood;
+ private int countOk;
+ private int countMeh;
+ private int countMiss;
+
+ public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
+ : base(ruleset, beatmap, score)
+ {
+ }
+
+ public override double Calculate(Dictionary categoryDifficulty = null)
+ {
+ mods = Score.Mods;
+ scaledScore = Score.TotalScore;
+ countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
+ countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+ countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+ countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
+ countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+ countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+
+ if (mods.Any(m => !m.Ranked))
+ return 0;
+
+ IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
+
+ double scoreMultiplier = 1.0;
+ foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
+ scoreMultiplier *= m.ScoreMultiplier;
+
+ // Scale score up, so it's comparable to other keymods
+ scaledScore *= 1.0 / scoreMultiplier;
+
+ // Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
+ // The specific number has no intrinsic meaning and can be adjusted as needed.
+ double multiplier = 0.8;
+
+ if (mods.Any(m => m is ModNoFail))
+ multiplier *= 0.9;
+ if (mods.Any(m => m is ModEasy))
+ multiplier *= 0.5;
+
+ double strainValue = computeStrainValue();
+ double accValue = computeAccuracyValue(strainValue);
+ double totalValue =
+ Math.Pow(
+ Math.Pow(strainValue, 1.1) +
+ Math.Pow(accValue, 1.1), 1.0 / 1.1
+ ) * multiplier;
+
+ if (categoryDifficulty != null)
+ {
+ categoryDifficulty["Strain"] = strainValue;
+ categoryDifficulty["Accuracy"] = accValue;
+ }
+
+ return totalValue;
+ }
+
+ private double computeStrainValue()
+ {
+ // Obtain strain difficulty
+ double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0;
+
+ // Longer maps are worth more
+ strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
+
+ if (scaledScore <= 500000)
+ strainValue = 0;
+ else if (scaledScore <= 600000)
+ strainValue *= (scaledScore - 500000) / 100000 * 0.3;
+ else if (scaledScore <= 700000)
+ strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
+ else if (scaledScore <= 800000)
+ strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
+ else if (scaledScore <= 900000)
+ strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
+ else
+ strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
+
+ return strainValue;
+ }
+
+ private double computeAccuracyValue(double strainValue)
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
+ if (hitWindowGreat <= 0)
+ return 0;
+
+ // Lots of arbitrary values from testing.
+ // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+ double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
+ * strainValue
+ * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
+
+ // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+ // accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+
+ return accuracyValue;
+ }
+
+ private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
new file mode 100644
index 0000000000..9630ba9273
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
@@ -0,0 +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.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Judgements
+{
+ public class HoldNoteJudgement : ManiaJudgement
+ {
+ public override bool AffectsCombo => false;
+ protected override int NumericResultFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 0546cbc174..02ecb3afda 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -15,12 +15,18 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Difficulty;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset);
+ public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+ public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
@@ -182,7 +188,7 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
+ public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
public override int? LegacyID => 3;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
index dbd30121a8..e02db68a28 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
@@ -3,19 +3,18 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter
+ public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter
{
public override string ShortenedName => Name;
public abstract int KeyCount { get; }
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
public override bool Ranked => true;
- public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter)
+ public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
{
var mbc = (ManiaBeatmapConverter)beatmapConverter;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index 197b37b3f5..256811b4c1 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -11,19 +11,23 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer
+ public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer
{
public override string Name => "Dual Stages";
public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!";
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
- public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter)
+ private bool isForCurrentRuleset;
+
+ public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
{
var mbc = (ManiaBeatmapConverter)beatmapConverter;
+ isForCurrentRuleset = mbc.IsForCurrentRuleset;
+
// Although this can work, for now let's not allow keymods for mania-specific beatmaps
- if (mbc.IsForCurrentRuleset)
+ if (isForCurrentRuleset)
return;
mbc.TargetColumns *= 2;
@@ -34,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Mods
var mrc = (ManiaRulesetContainer)rulesetContainer;
// Although this can work, for now let's not allow keymods for mania-specific beatmaps
- if (mrc.IsForCurrentRuleset)
+ if (isForCurrentRuleset)
return;
var newDefinitions = new List();
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 5af898287a..2f951461c3 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string ShortenedName => "RD";
public override FontAwesome Icon => FontAwesome.fa_osu_dice;
public override string Description => @"Shuffle around the keys!";
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index f8b2311a13..f7de503fb3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHandler
{
+ public override bool DisplayJudgement => false;
+
private readonly DrawableNote head;
private readonly DrawableNote tail;
@@ -99,6 +101,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
+ switch (state)
+ {
+ case ArmedState.Hit:
+ // Good enough for now, we just want them to have a lifetime end
+ this.Delay(2000).Expire();
+ break;
+ }
+ }
+
+ protected override void CheckForJudgements(bool userTriggered, double timeOffset)
+ {
+ if (tail.AllJudged)
+ AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect });
}
protected override void Update()
@@ -191,6 +206,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private class DrawableTailNote : DrawableNote
{
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ private const double release_window_lenience = 1.5;
+
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
@@ -203,6 +225,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
+ // Factor in the release lenience
+ timeOffset /= release_window_lenience;
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 4cf22ccd39..22fa93a308 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Objects
///
/// The tail note of the hold.
///
- public readonly Note Tail = new TailNote();
+ public readonly Note Tail = new Note();
///
/// The time between ticks of this hold.
@@ -94,24 +94,5 @@ namespace osu.Game.Rulesets.Mania.Objects
});
}
}
-
- ///
- /// The tail of the hold note.
- ///
- private class TailNote : Note
- {
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- ///
- private const double release_window_lenience = 1.5;
-
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
- {
- base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
-
- HitWindows *= release_window_lenience;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 4f0e02ff0d..e183098a51 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -1,8 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -12,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public virtual int Column { get; set; }
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
- {
- base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
-
- HitWindows.AllowsPerfect = true;
- HitWindows.AllowsOk = true;
- }
+ protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
new file mode 100644
index 0000000000..063b626af1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
@@ -0,0 +1,36 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Objects
+{
+ public class ManiaHitWindows : HitWindows
+ {
+ private static readonly IReadOnlyDictionary base_ranges = new Dictionary
+ {
+ { HitResult.Perfect, (44.8, 38.8, 27.8) },
+ { HitResult.Great, (128, 98, 68 ) },
+ { HitResult.Good, (194, 164, 134) },
+ { HitResult.Ok, (254, 224, 194) },
+ { HitResult.Meh, (302, 272, 242) },
+ { HitResult.Miss, (376, 346, 316) },
+ };
+
+ 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]);
+ Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
+ Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
+ Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 8d86325dd9..bc9fd6e06f 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Mania.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
{
// We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap);
+ var converter = new ManiaBeatmapConverter(beatmap);
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 52d514221d..28cd1b6b39 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
- if (!judgement.IsHit)
+ if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return;
explosionContainer.Add(new HitExplosion(judgedObject));
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 76afaf270f..7123aab901 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
- public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
// Generate the bar lines
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
@@ -85,8 +85,6 @@ namespace osu.Game.Rulesets.Mania.UI
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
- protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap);
-
protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h)
{
ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index 904be3a9e3..605794c795 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -171,6 +171,9 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
+ if (!judgedObject.DisplayJudgement)
+ return;
+
judgements.Clear();
judgements.Add(new DrawableManiaJudgement(judgement, judgedObject)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 6ac3c016a0..386ae5eb05 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -5,17 +5,15 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class OsuBeatmapConversionTest : BeatmapConversionTest
+ internal class OsuBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
@@ -42,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Tests
};
}
- protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter();
+ protected override Ruleset CreateRuleset() => new OsuRuleset();
}
- public struct ConvertValue : IEquatable
+ internal struct ConvertValue : IEquatable
{
///
/// A sane value to account for osu!stable using ints everwhere.
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
index f7f73f74a5..cb1ea5cc5f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
@@ -93,12 +93,36 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
+
+ AddStep("Distance Overflow", () => testDistanceOverflow());
+ AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1));
}
private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
+ private void testDistanceOverflow(int repeats = 0)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(239, 176),
+ ControlPoints = new List
+ {
+ Vector2.Zero,
+ new Vector2(154, 28),
+ new Vector2(52, -34)
+ },
+ Distance = 700,
+ RepeatCount = repeats,
+ RepeatSamples = createEmptySamples(repeats),
+ StackHeight = 10
+ };
+
+ addSlider(slider, 2, 2);
+ }
+
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
new file mode 100644
index 0000000000..6d90c2a875
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
@@ -0,0 +1,43 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Beatmaps
+{
+ public class OsuBeatmap : Beatmap
+ {
+ public override IEnumerable GetStatistics()
+ {
+ int circles = HitObjects.Count(c => c is HitCircle);
+ int sliders = HitObjects.Count(s => s is Slider);
+ int spinners = HitObjects.Count(s => s is Spinner);
+
+ return new[]
+ {
+ new BeatmapStatistic
+ {
+ Name = @"Circle Count",
+ Content = circles.ToString(),
+ Icon = FontAwesome.fa_circle_o
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Slider Count",
+ Content = sliders.ToString(),
+ Icon = FontAwesome.fa_circle
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Spinner Count",
+ Content = spinners.ToString(),
+ Icon = FontAwesome.fa_circle
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 1236076f48..80eb808f6e 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -14,9 +14,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
internal class OsuBeatmapConverter : BeatmapConverter
{
+ public OsuBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
- protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
var curveData = original as IHasCurve;
var endTimeData = original as IHasEndTime;
@@ -45,8 +50,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
EndTime = endTimeData.EndTime,
-
- Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
+ Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2
};
}
else
@@ -60,5 +64,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
};
}
}
+
+ protected override Beatmap CreateBeatmap() => new OsuBeatmap();
}
}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index afa2437bf6..c7c9f4a01a 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -8,12 +8,17 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Beatmaps
{
- internal class OsuBeatmapProcessor : BeatmapProcessor
+ internal class OsuBeatmapProcessor : BeatmapProcessor
{
- public override void PostProcess(Beatmap beatmap)
+ public OsuBeatmapProcessor(IBeatmap beatmap)
+ : base(beatmap)
{
- applyStacking(beatmap);
- base.PostProcess(beatmap);
+ }
+
+ public override void PostProcess()
+ {
+ applyStacking((Beatmap)Beatmap);
+ base.PostProcess();
}
private void applyStacking(Beatmap beatmap)
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
similarity index 64%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
rename to osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 926a7975f3..3ed072a275 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -4,55 +4,54 @@
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty
+namespace osu.Game.Rulesets.Osu.Difficulty
{
- public class OsuDifficultyCalculator : DifficultyCalculator
+ public class OsuDifficultyCalculator : DifficultyCalculator
{
private const int section_length = 400;
private const double difficulty_multiplier = 0.0675;
- public OsuDifficultyCalculator(Beatmap beatmap)
+ public OsuDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{
}
- public OsuDifficultyCalculator(Beatmap beatmap, Mod[] mods)
+ public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
: base(beatmap, mods)
{
}
- protected override void PreprocessHitObjects()
- {
- new OsuBeatmapProcessor().PostProcess(Beatmap);
- }
-
public override double Calculate(Dictionary categoryDifficulty = null)
{
- OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
+ OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List)Beatmap.HitObjects, TimeRate);
Skill[] skills =
{
new Aim(),
new Speed()
};
- double sectionEnd = section_length / TimeRate;
+ double sectionLength = section_length * TimeRate;
+
+ // The first object doesn't generate a strain, so we begin with an incremented section end
+ double currentSectionEnd = 2 * sectionLength;
+
foreach (OsuDifficultyHitObject h in beatmap)
{
- while (h.BaseObject.StartTime > sectionEnd)
+ while (h.BaseObject.StartTime > currentSectionEnd)
{
foreach (Skill s in skills)
{
s.SaveCurrentPeak();
- s.StartNewSectionFrom(sectionEnd);
+ s.StartNewSectionFrom(currentSectionEnd);
}
- sectionEnd += section_length;
+ currentSectionEnd += sectionLength;
}
foreach (Skill s in skills)
@@ -72,7 +71,5 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
return starRating;
}
-
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new OsuBeatmapConverter();
}
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
similarity index 71%
rename from osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
rename to osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 8f0feca207..57cf962fa7 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -5,35 +5,46 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Osu.Scoring
+namespace osu.Game.Rulesets.Osu.Difficulty
{
- public class OsuPerformanceCalculator : PerformanceCalculator
+ public class OsuPerformanceCalculator : PerformanceCalculator
{
private readonly int countHitCircles;
private readonly int beatmapMaxCombo;
private Mod[] mods;
+
+ ///
+ /// Approach rate adjusted by mods.
+ ///
private double realApproachRate;
+
+ ///
+ /// Overall difficulty adjusted by mods.
+ ///
+ private double realOverallDifficulty;
+
private double accuracy;
private int scoreMaxCombo;
- private int count300;
- private int count100;
- private int count50;
+ private int countGreat;
+ private int countGood;
+ private int countMeh;
private int countMiss;
- public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+ public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
- beatmapMaxCombo = Beatmap.HitObjects.Count;
- beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1;
+ beatmapMaxCombo = Beatmap.HitObjects.Count();
+ // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
+ beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
}
public override double Calculate(Dictionary categoryRatings = null)
@@ -41,25 +52,21 @@ namespace osu.Game.Rulesets.Osu.Scoring
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
- count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
- count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
- count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+ countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+ countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+ countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
return 0;
- // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
- // locally for now as doing so would modify animations and other things unexpectedly
- // DO NOT MODIFY THIS
- double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
- if (mods.Any(m => m is OsuModHardRock))
- ar = Math.Min(10, ar * 1.4);
- if (mods.Any(m => m is OsuModEasy))
- ar = Math.Max(0, ar / 2);
- double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
+ // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
+ double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
+
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
+ realOverallDifficulty = (80 - hitWindowGreat) / 6;
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -85,6 +92,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
+ categoryRatings.Add("OD", realOverallDifficulty);
+ categoryRatings.Add("AR", realApproachRate);
+ categoryRatings.Add("Max Combo", beatmapMaxCombo);
}
return totalValue;
@@ -121,8 +131,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
aimValue *= approachRateFactor;
+ // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
- aimValue *= 1.18f;
+ aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight))
{
@@ -133,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+ aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
return aimValue;
}
@@ -153,10 +164,13 @@ namespace osu.Game.Rulesets.Osu.Scoring
if (beatmapMaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+ if (mods.Any(m => m is OsuModHidden))
+ speedValue *= 1.18f;
+
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+ speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
return speedValue;
}
@@ -168,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
- betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
+ betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
@@ -178,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+ double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
@@ -191,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
return accuracyValue;
}
- private double totalHits => count300 + count100 + count50 + countMiss;
- private double totalSuccessfulHits => count300 + count100 + count50;
-
- protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter();
+ private double totalHits => countGreat + countGood + countMeh + countMiss;
+ private double totalSuccessfulHits => countGreat + countGood + countMeh;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
new file mode 100644
index 0000000000..4443a0e66b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
+{
+ ///
+ /// An enumerable container wrapping input as
+ /// which contains extra data required for difficulty calculation.
+ ///
+ public class OsuDifficultyBeatmap : IEnumerable
+ {
+ private readonly IEnumerator difficultyObjects;
+
+ ///
+ /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as
+ /// which contains extra data required for difficulty calculation.
+ ///
+ public OsuDifficultyBeatmap(List objects, double timeRate)
+ {
+ // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
+ // This should probably happen before the objects reach the difficulty calculator.
+ objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
+ difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
+ }
+
+ ///
+ /// Returns an enumerator that enumerates all s in the .
+ ///
+ public IEnumerator GetEnumerator() => difficultyObjects;
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate)
+ {
+ // The first jump is formed by the first two hitobjects of the map.
+ // If the map has less than two OsuHitObjects, the enumerator will not return anything.
+ for (int i = 1; i < objects.Count; i++)
+ yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
similarity index 82%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 415f76ced8..29de23406b 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -3,16 +3,18 @@
using System;
using System.Linq;
-using OpenTK;
using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
+namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
///
/// A wrapper around extending it with additional data required for difficulty calculation.
///
public class OsuDifficultyHitObject
{
+ private const int normalized_radius = 52;
+
///
/// The this refers to.
///
@@ -28,26 +30,19 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
///
public double DeltaTime { get; private set; }
- ///
- /// Number of milliseconds until the has to be hit.
- ///
- public double TimeUntilHit { get; set; }
-
- private const int normalized_radius = 52;
-
+ private readonly OsuHitObject lastObject;
private readonly double timeRate;
- private readonly OsuHitObject[] t;
-
///
/// Initializes the object calculating extra data required for difficulty calculation.
///
- public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
+ public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate)
{
+ this.lastObject = lastObject;
this.timeRate = timeRate;
- t = triangle;
- BaseObject = t[0];
+ BaseObject = currentObject;
+
setDistances();
setTimingValues();
// Calculate angle here
@@ -63,10 +58,10 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
- Vector2 lastCursorPosition = t[1].StackedPosition;
+ Vector2 lastCursorPosition = lastObject.StackedPosition;
float lastTravelDistance = 0;
- var lastSlider = t[1] as Slider;
+ var lastSlider = lastObject as Slider;
if (lastSlider != null)
{
computeSliderCursorPosition(lastSlider);
@@ -80,8 +75,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
private void setTimingValues()
{
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
- DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
- TimeUntilHit = 450; // BaseObject.PreEmpt;
+ DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
}
private void computeSliderCursorPosition(Slider slider)
@@ -107,7 +101,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
}
});
- var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime);
+ // Skip the head circle
+ var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime);
foreach (var time in scoringTimes)
computeVertex(time);
computeVertex(slider.EndTime);
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
similarity index 85%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 5c736d7bb5..0a45c62671 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
///
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
similarity index 96%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
index 983599432f..47037c1503 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
@@ -3,11 +3,11 @@
using System;
using System.Collections.Generic;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Utils;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Utils;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
///
/// Used to processes strain values of s, keep track of strain levels caused by the processed objects
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
similarity index 93%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index ae3caa1e66..b807f20037 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -1,9 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
///
/// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
similarity index 98%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
index f6933a3e5d..55bd950209 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
+namespace osu.Game.Rulesets.Osu.Difficulty.Utils
{
///
/// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full.
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
index 8d4c342740..ea33ec9ae0 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
@@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuEditRulesetContainer : OsuRulesetContainer
{
- public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 7bf0651443..dce1fc2851 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true);
+ protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap);
protected override IReadOnlyList CompositionTools => new ICompositionTool[]
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 3573c133c2..b2ddd65e38 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string ShortenedName => "AP";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
- public override double ScoreMultiplier => 0;
- public override bool Ranked => false;
+ public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index cf71116d47..7a30e6b134 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -5,20 +5,23 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHardRock : ModHardRock, IApplicableToHitObject
+ public class OsuModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1.06;
public override bool Ranked => true;
- public void ApplyToHitObject(OsuHitObject hitObject)
+ public void ApplyToHitObject(HitObject hitObject)
{
- hitObject.Position = new Vector2(hitObject.Position.X, OsuPlayfield.BASE_SIZE.Y - hitObject.Y);
+ var osuObject = (OsuHitObject)hitObject;
+
+ osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
var slider = hitObject as Slider;
if (slider == null)
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 2b7b7783e2..54126b934f 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -71,5 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public virtual void OffsetPosition(Vector2 offset) => Position += offset;
+
+ protected override HitWindows CreateHitWindows() => new OsuHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
new file mode 100644
index 0000000000..8405498554
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
@@ -0,0 +1,29 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Objects
+{
+ public class OsuHitWindows : HitWindows
+ {
+ private static readonly IReadOnlyDictionary base_ranges = new Dictionary
+ {
+ { HitResult.Great, (160, 100, 40) },
+ { HitResult.Good, (280, 200, 120) },
+ { HitResult.Meh, (400, 300, 200) },
+ { HitResult.Miss, (400, 400, 400) },
+ };
+
+ 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.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
deleted file mode 100644
index 5c8ab0f3d4..0000000000
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System.Collections;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Osu.Objects;
-
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
-{
- ///
- /// An enumerable container wrapping input as
- /// which contains extra data required for difficulty calculation.
- ///
- public class OsuDifficultyBeatmap : IEnumerable
- {
- private readonly IEnumerator difficultyObjects;
- private readonly Queue onScreen = new Queue();
-
- ///
- /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as
- /// which contains extra data required for difficulty calculation.
- ///
- public OsuDifficultyBeatmap(List objects, double timeRate)
- {
- // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
- // This should probably happen before the objects reach the difficulty calculator.
- objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
- difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
- }
-
- ///
- /// Returns an enumerator that enumerates all s in the .
- /// The inner loop adds objects that appear on screen into a queue until we need to hit the next object.
- /// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen.
- /// This means that we can loop through every object that is on screen at the time when a new one appears,
- /// allowing us to determine a reading strain for the object that just appeared.
- ///
- public IEnumerator GetEnumerator()
- {
- while (true)
- {
- // Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued.
- // This means there is always at least one object in the queue unless we reached the end of the map.
- do
- {
- if (!difficultyObjects.MoveNext())
- break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen.
-
- OsuDifficultyHitObject latest = difficultyObjects.Current;
- // Calculate flow values here
-
- foreach (OsuDifficultyHitObject h in onScreen)
- {
- // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext())
- h.TimeUntilHit -= latest.DeltaTime;
- // Calculate reading strain here
- }
-
- onScreen.Enqueue(latest);
- }
- while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one.
-
- if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects.
- yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared.
- }
- }
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate)
- {
- // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
- OsuHitObject[] triangle = new OsuHitObject[3];
-
- // OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning.
- if (objects.Count > 1)
- {
- triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle.
- triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject.
- }
-
- // The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump.
- // If the map has less than two OsuHitObjects, the enumerator will not return anything.
- for (int i = 1; i < objects.Count; ++i)
- {
- triangle[2] = triangle[1];
- triangle[1] = triangle[0];
- triangle[0] = objects[i];
-
- yield return new OsuDifficultyHitObject(triangle, timeRate);
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index e0ecee97a3..c455bb2af6 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -5,29 +5,29 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Difficulty;
namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset);
+ public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+ public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
@@ -37,36 +37,6 @@ namespace osu.Game.Rulesets.Osu
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
- public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap)
- {
- IEnumerable hitObjects = beatmap.Beatmap.HitObjects;
- IEnumerable circles = hitObjects.Where(c => !(c is IHasEndTime));
- IEnumerable sliders = hitObjects.Where(s => s is IHasCurve);
- IEnumerable spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
-
- return new[]
- {
- new BeatmapStatistic
- {
- Name = @"Circle Count",
- Content = circles.Count().ToString(),
- Icon = FontAwesome.fa_circle_o
- },
- new BeatmapStatistic
- {
- Name = @"Slider Count",
- Content = sliders.Count().ToString(),
- Icon = FontAwesome.fa_circle
- },
- new BeatmapStatistic
- {
- Name = @"Spinner Count",
- Content = spinners.Count().ToString(),
- Icon = FontAwesome.fa_circle
- }
- };
- }
-
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
@@ -181,9 +151,9 @@ namespace osu.Game.Rulesets.Osu
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
+ public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
- public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
+ public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index 6f2512cc33..4412b6efab 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
{
Position = legacyFrame.Position;
if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 22c7b719cd..ad1052f86a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -7,7 +7,6 @@ using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
@@ -21,17 +20,13 @@ namespace osu.Game.Rulesets.Osu.UI
{
public class OsuRulesetContainer : RulesetContainer
{
- public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
- protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter();
-
- protected override BeatmapProcessor CreateBeatmapProcessor() => new OsuBeatmapProcessor();
-
protected override Playfield CreatePlayfield() => new OsuPlayfield();
public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index aa61f2d60b..11586e340b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -5,27 +5,22 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
- public class TaikoBeatmapConversionTest : BeatmapConversionTest
+ internal class TaikoBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
- private bool isForCurrentRuleset;
-
[NonParallelizable]
- [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
- [TestCase("slider-generating-drumroll", false)]
- public void Test(string name, bool isForCurrentRuleset)
+ [TestCase("basic")]
+ [TestCase("slider-generating-drumroll")]
+ public new void Test(string name)
{
- this.isForCurrentRuleset = isForCurrentRuleset;
base.Test(name);
}
@@ -43,10 +38,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
};
}
- protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
}
- public struct ConvertValue : IEquatable
+ internal struct ConvertValue : IEquatable
{
///
/// A sane value to account for osu!stable using ints everwhere.
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
index aa7318b863..1bf24a46bc 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
private Container playfieldContainer;
[BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
+ private void load()
{
AddStep("Hit!", () => addHitJudgement(false));
AddStep("Kiai hit", () => addHitJudgement(true));
@@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Title = @"Sample Beatmap",
AuthorString = @"peppy",
},
+ Ruleset = new TaikoRuleset().RulesetInfo
},
ControlPointInfo = controlPointInfo
});
@@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
RelativeSizeAxes = Axes.X,
Height = 768,
Clock = new FramedClock(rateAdjustClock),
- Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) }
+ Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) }
});
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
new file mode 100644
index 0000000000..36f6df7869
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
@@ -0,0 +1,43 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Beatmaps
+{
+ public class TaikoBeatmap : Beatmap
+ {
+ public override IEnumerable GetStatistics()
+ {
+ int hits = HitObjects.Count(s => s is Hit);
+ int drumrolls = HitObjects.Count(s => s is DrumRoll);
+ int swells = HitObjects.Count(s => s is Swell);
+
+ return new[]
+ {
+ new BeatmapStatistic
+ {
+ Name = @"Hit Count",
+ Content = hits.ToString(),
+ Icon = FontAwesome.fa_circle_o
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Drumroll Count",
+ Content = drumrolls.ToString(),
+ Icon = FontAwesome.fa_circle
+ },
+ new BeatmapStatistic
+ {
+ Name = @"Swell Count",
+ Content = swells.ToString(),
+ Icon = FontAwesome.fa_circle
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 2f175a9922..41972b5d20 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Taiko.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Game.IO.Serialization;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
@@ -42,16 +41,18 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) };
- public TaikoBeatmapConverter(bool isForCurrentRuleset)
+ public TaikoBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
{
- this.isForCurrentRuleset = isForCurrentRuleset;
+ isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo);
}
- protected override Beatmap ConvertBeatmap(Beatmap original)
+ protected override Beatmap ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
- BeatmapInfo info = original.BeatmapInfo.DeepClone();
- info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
+ original.BeatmapInfo = original.BeatmapInfo.Clone();
+ original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
+ original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap converted = base.ConvertBeatmap(original);
@@ -70,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
return converted;
}
- protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
var distanceData = obj as IHasDistance;
var repeatsData = obj as IHasRepeats;
@@ -97,12 +98,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
- double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
// The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
@@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = j,
Samples = currentSamples,
- IsStrong = strong,
+ IsStrong = strong
};
}
@@ -155,7 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = taikoDuration,
- TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
+ TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
};
}
}
@@ -169,7 +170,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = endTimeData.Duration,
- RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
+ RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
};
}
else
@@ -182,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
- IsStrong = strong,
+ IsStrong = strong
};
}
else
@@ -191,10 +192,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
- IsStrong = strong,
+ IsStrong = strong
};
}
}
}
+
+ protected override Beatmap CreateBeatmap() => new TaikoBeatmap();
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
similarity index 91%
rename from osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
rename to osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 58661d7881..57e1e65064 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -1,15 +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.Beatmaps;
-using osu.Game.Rulesets.Taiko.Beatmaps;
-using osu.Game.Rulesets.Taiko.Objects;
-using System.Collections.Generic;
using System;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
-namespace osu.Game.Rulesets.Taiko
+namespace osu.Game.Rulesets.Taiko.Difficulty
{
- internal class TaikoDifficultyCalculator : DifficultyCalculator
+ internal class TaikoDifficultyCalculator : DifficultyCalculator
{
private const double star_scaling_factor = 0.04125;
@@ -30,18 +31,23 @@ namespace osu.Game.Rulesets.Taiko
///
private readonly List difficultyHitObjects = new List();
- public TaikoDifficultyCalculator(Beatmap beatmap)
+ public TaikoDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{
}
+ public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
+ : base(beatmap, mods)
+ {
+ }
+
public override double Calculate(Dictionary categoryDifficulty = null)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
foreach (var hitObject in Beatmap.HitObjects)
- difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject));
+ difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
@@ -51,10 +57,7 @@ namespace osu.Game.Rulesets.Taiko
double starRating = calculateDifficulty() * star_scaling_factor;
if (categoryDifficulty != null)
- {
- categoryDifficulty.Add("Strain", starRating);
- categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
- }
+ categoryDifficulty["Strain"] = starRating;
return starRating;
}
@@ -132,7 +135,5 @@ namespace osu.Game.Rulesets.Taiko
return difficulty;
}
-
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new TaikoBeatmapConverter(true);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
new file mode 100644
index 0000000000..6b1a25d667
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -0,0 +1,112 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty
+{
+ public class TaikoPerformanceCalculator : PerformanceCalculator
+ {
+ private readonly int beatmapMaxCombo;
+
+ private Mod[] mods;
+ private int countGreat;
+ private int countGood;
+ private int countMeh;
+ private int countMiss;
+
+ public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
+ : base(ruleset, beatmap, score)
+ {
+ beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
+ }
+
+ public override double Calculate(Dictionary categoryDifficulty = null)
+ {
+ mods = Score.Mods;
+ countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+ countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+ countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+ countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+
+ // Don't count scores made with supposedly unranked mods
+ if (mods.Any(m => !m.Ranked))
+ return 0;
+
+ // Custom multipliers for NoFail and SpunOut.
+ double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+
+ if (mods.Any(m => m is ModNoFail))
+ multiplier *= 0.90;
+
+ if (mods.Any(m => m is ModHidden))
+ multiplier *= 1.10;
+
+ double strainValue = computeStrainValue();
+ double accuracyValue = computeAccuracyValue();
+ double totalValue =
+ Math.Pow(
+ Math.Pow(strainValue, 1.1) +
+ Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
+ ) * multiplier;
+
+ if (categoryDifficulty != null)
+ {
+ categoryDifficulty["Strain"] = strainValue;
+ categoryDifficulty["Accuracy"] = accuracyValue;
+ }
+
+ return totalValue;
+ }
+
+ private double computeStrainValue()
+ {
+ double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0;
+
+ // Longer maps are worth more
+ double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
+ strainValue *= lengthBonus;
+
+ // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+ strainValue *= Math.Pow(0.985, countMiss);
+
+ // Combo scaling
+ if (beatmapMaxCombo > 0)
+ strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
+
+ if (mods.Any(m => m is ModHidden))
+ strainValue *= 1.025;
+
+ if (mods.Any(m => m is ModFlashlight))
+ // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
+ strainValue *= 1.05 * lengthBonus;
+
+ // Scale the speed value with accuracy _slightly_
+ return strainValue * Score.Accuracy;
+ }
+
+ private double computeAccuracyValue()
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
+ if (hitWindowGreat <= 0)
+ return 0;
+
+ // Lots of arbitrary values from testing.
+ // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+ double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
+
+ // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+ return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+ }
+
+ private int totalHits => countGreat + countGood + countMeh + countMiss;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 64219c7b52..4c9ec5473b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
///
private double tickSpacing = 100;
+ private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -47,9 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / TickRate;
-
- RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
- RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
+ overallDifficulty = difficulty.OverallDifficulty;
}
protected override void CreateNestedHitObjects()
@@ -57,6 +57,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.CreateNestedHitObjects();
createTicks();
+
+ RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty);
+ RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty);
}
private void createTicks()
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 63de096238..ffbbe28f2e 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -27,5 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// Strong hit objects give more points for hitting the hit object with both keys.
///
public bool IsStrong;
+
+ protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
new file mode 100644
index 0000000000..289f084a45
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
@@ -0,0 +1,29 @@
+// 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 osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Objects
+{
+ public class TaikoHitWindows : HitWindows
+ {
+ private static readonly IReadOnlyDictionary base_ranges = new Dictionary
+ {
+ { HitResult.Great, (100, 70, 40) },
+ { HitResult.Good, (240, 160, 100) },
+ { HitResult.Meh, (270, 190, 140) },
+ { HitResult.Miss, (400, 400, 400) },
+ };
+
+ 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/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
index e510b34ad7..2177a3cbdc 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
{
if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 06a8e44a14..abaa8db597 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -13,12 +13,17 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Difficulty;
namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset);
+ public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
@@ -140,7 +145,9 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
+ public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
+
+ public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 1;
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 3d3c6ab2f3..313c205981 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
@@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoRulesetContainer : ScrollingRulesetContainer
{
- public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
@@ -93,8 +92,6 @@ namespace osu.Game.Rulesets.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
- protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
-
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo)
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 6e0cf6be2e..489c38c420 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -9,6 +9,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Resources;
using OpenTK;
@@ -117,7 +118,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestParity(string beatmap)
{
var legacy = decode(beatmap, out Beatmap json);
- json.ShouldDeepEqual(legacy);
+ json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert();
}
///
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 6453cdbd3e..586217a05f 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Beatmaps.IO
{
@@ -77,8 +78,69 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestRollbackOnFailure()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ int fireCount = 0;
+
+ // ReSharper disable once AccessToModifiedClosure
+ manager.ItemAdded += _ => fireCount++;
+ manager.ItemRemoved += _ => fireCount++;
+
+ var imported = loadOszIntoOsu(osu);
+
+ Assert.AreEqual(0, fireCount -= 1);
+
+ imported.Hash += "-changed";
+ manager.Update(imported);
+
+ Assert.AreEqual(0, fireCount -= 2);
+
+ var breakTemp = createTemporaryBeatmap();
+
+ MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
+ MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
+
+ File.Delete(breakTemp);
+
+ using (var outStream = File.Open(breakTemp, FileMode.CreateNew))
+ using (var zip = ZipArchive.Open(brokenOsz))
+ {
+ zip.AddEntry("broken.osu", brokenOsu, false);
+ zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
+ }
+
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
+
+ // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
+ manager.Import(breakTemp);
+
+ // no events should be fired in the case of a rollback.
+ Assert.AreEqual(0, fireCount);
+
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
}
finally
{
@@ -100,18 +162,17 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = loadOszIntoOsu(osu);
- //var change = manager.QueryBeatmapSets(_ => true).First();
imported.Hash += "-changed";
manager.Update(imported);
var importedSecondTime = loadOszIntoOsu(osu);
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ // only one beatmap will exist as the online set ID matched, causing purging of the first import.
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
}
finally
{
@@ -162,8 +223,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
- var temp = prepareTempCopy(osz_path);
- Assert.IsTrue(File.Exists(temp));
+ var temp = createTemporaryBeatmap();
var importer = new ArchiveImportIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(10000))
@@ -188,8 +248,7 @@ namespace osu.Game.Tests.Beatmaps.IO
try
{
var osu = loadOsu(host);
- var temp = prepareTempCopy(osz_path);
- Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
+ var temp = createTemporaryBeatmap();
using (File.OpenRead(temp))
osu.Dependencies.Get().Import(temp);
ensureLoaded(osu);
@@ -203,11 +262,16 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu)
+ private string createTemporaryBeatmap()
{
- var temp = prepareTempCopy(osz_path);
-
+ var temp = new FileInfo(osz_path).CopyTo(Path.GetTempFileName(), true).FullName;
Assert.IsTrue(File.Exists(temp));
+ return temp;
+ }
+
+ private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null)
+ {
+ var temp = path ?? createTemporaryBeatmap();
var manager = osu.Dependencies.Get();
@@ -219,7 +283,7 @@ namespace osu.Game.Tests.Beatmaps.IO
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return imported.FirstOrDefault();
+ return imported.LastOrDefault();
}
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
@@ -228,16 +292,10 @@ namespace osu.Game.Tests.Beatmaps.IO
manager.Delete(imported);
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
- private string prepareTempCopy(string path)
- {
- var temp = Path.GetTempFileName();
- return new FileInfo(path).CopyTo(temp, true).FullName;
- }
-
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
@@ -275,13 +333,13 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
Assert.IsTrue(set.Beatmaps.Count > 0);
var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+ Assert.IsTrue(beatmap?.HitObjects.Any() == true);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+ Assert.IsTrue(beatmap?.HitObjects.Any() == true);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+ Assert.IsTrue(beatmap?.HitObjects.Any() == true);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+ Assert.IsTrue(beatmap?.HitObjects.Any() == true);
}
private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs
index cecb327b6c..bf7aa725e5 100644
--- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs
@@ -5,6 +5,7 @@ using System.ComponentModel;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@@ -15,7 +16,20 @@ namespace osu.Game.Tests.Visual
protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset)
{
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
- return base.CreatePlayer(beatmap, ruleset);
+ return new ScoreAccessiblePlayer
+ {
+ InitialBeatmap = beatmap,
+ AllowPause = false,
+ AllowLeadIn = false,
+ AllowResults = false,
+ };
+ }
+
+ protected override bool ContinueCondition(Player player) => base.ContinueCondition(player) && ((ScoreAccessiblePlayer)player).ScoreProcessor.TotalScore > 0;
+
+ private class ScoreAccessiblePlayer : Player
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index 886c1120d4..0d3e08154f 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -12,8 +12,12 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
@@ -24,7 +28,7 @@ namespace osu.Game.Tests.Visual
{
private RulesetStore rulesets;
private TestBeatmapInfoWedge infoWedge;
- private readonly List beatmaps = new List();
+ private readonly List beatmaps = new List();
private readonly Bindable beatmap = new Bindable();
[BackgroundDependencyLoader]
@@ -72,13 +76,23 @@ namespace osu.Game.Tests.Visual
selectBeatmap(testBeatmap);
+ testBeatmapLabels(ruleset);
+
// TODO: adjust cases once more info is shown for other gamemodes
switch (ruleset)
{
- case OsuRuleset osu:
- testOsuBeatmap(osu);
+ case OsuRuleset _:
testInfoLabels(5);
break;
+ case TaikoRuleset _:
+ testInfoLabels(5);
+ break;
+ case CatchRuleset _:
+ testInfoLabels(5);
+ break;
+ case ManiaRuleset _:
+ testInfoLabels(4);
+ break;
default:
testInfoLabels(2);
break;
@@ -88,7 +102,7 @@ namespace osu.Game.Tests.Visual
testNullBeatmap();
}
- private void testOsuBeatmap(OsuRuleset ruleset)
+ private void testBeatmapLabels(Ruleset ruleset)
{
AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
@@ -112,7 +126,7 @@ namespace osu.Game.Tests.Visual
AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
}
- private void selectBeatmap(Beatmap b)
+ private void selectBeatmap(IBeatmap b)
{
BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null;
@@ -134,11 +148,11 @@ namespace osu.Game.Tests.Visual
});
}
- private Beatmap createTestBeatmap(RulesetInfo ruleset)
+ private IBeatmap createTestBeatmap(RulesetInfo ruleset)
{
List objects = new List();
for (double i = 0; i < 50000; i += 1000)
- objects.Add(new HitObject { StartTime = i });
+ objects.Add(new TestHitObject { StartTime = i });
return new Beatmap
{
@@ -153,7 +167,8 @@ namespace osu.Game.Tests.Visual
},
Ruleset = ruleset,
StarDifficulty = 6,
- Version = $"{ruleset.ShortName}Version"
+ Version = $"{ruleset.ShortName}Version",
+ BaseDifficulty = new BeatmapDifficulty()
},
HitObjects = objects
};
@@ -163,5 +178,12 @@ namespace osu.Game.Tests.Visual
{
public new BufferedWedgeInfo Info => base.Info;
}
+
+ private class TestHitObject : HitObject, IHasPosition
+ {
+ public float X { get; } = 0;
+ public float Y { get; } = 0;
+ public Vector2 Position { get; } = Vector2.Zero;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
index a6a6130a8f..442ca1322a 100644
--- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using System.Collections.Generic;
using NUnit.Framework;
@@ -16,8 +15,6 @@ namespace osu.Game.Tests.Visual
public TestCaseBreakOverlay()
{
- Clock = new FramedClock();
-
Child = breakOverlay = new BreakOverlay(true);
AddStep("2s break", () => startBreak(2000));
diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs
index 977f241f7a..0aa8e9691e 100644
--- a/osu.Game.Tests/Visual/TestCaseCursors.cs
+++ b/osu.Game.Tests/Visual/TestCaseCursors.cs
@@ -19,12 +19,12 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseCursors : ManualInputManagerTestCase
{
- private readonly CursorOverrideContainer cursorOverrideContainer;
+ private readonly MenuCursorContainer menuCursorContainer;
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
public TestCaseCursors()
{
- Child = cursorOverrideContainer = new CursorOverrideContainer
+ Child = menuCursorContainer = new MenuCursorContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
AddStep("Move out", moveOut);
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
- AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
+ AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
}
///
@@ -112,11 +112,11 @@ namespace osu.Game.Tests.Visual
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
- AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
- AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor));
+ AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
+ AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor));
AddStep("Move out", moveOut);
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
- AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
+ AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
}
///
diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index 25f8ba06c4..8fd8880fd6 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -6,9 +6,10 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
@@ -111,6 +112,7 @@ namespace osu.Game.Tests.Visual
}
});
+ AddStep(@"select", () => first.State = SelectionState.Selected);
AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name");
AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying());
@@ -121,6 +123,7 @@ namespace osu.Game.Tests.Visual
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } },
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } },
});
+ AddStep(@"deselect", () => first.State = SelectionState.NotSelected);
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
index 582ab5ecc9..f037d70493 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
@@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual
private readonly Drawable tracker;
- public TimingPointVisualiser(Beatmap beatmap, double length)
+ public TimingPointVisualiser(IBeatmap beatmap, double length)
{
this.length = length;
diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs
new file mode 100644
index 0000000000..7d8535f428
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.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 System;
+using System.Collections.Generic;
+using osu.Game.Graphics.UserInterface;
+using OpenTK;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseExternalLinkButton : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[] { typeof(ExternalLinkButton) };
+
+ public TestCaseExternalLinkButton()
+ {
+ Child = new ExternalLinkButton("https://osu.ppy.sh/home")
+ {
+ Size = new Vector2(50)
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
new file mode 100644
index 0000000000..6fa49c4edb
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
@@ -0,0 +1,62 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseHoldToConfirmOverlay : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay) };
+
+ public TestCaseHoldToConfirmOverlay()
+ {
+ bool fired = false;
+
+ var firedText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Fired!",
+ TextSize = 50,
+ Alpha = 0,
+ };
+
+ var overlay = new TestHoldToConfirmOverlay
+ {
+ Action = () =>
+ {
+ fired = true;
+ firedText.FadeTo(1).Then().FadeOut(1000);
+ }
+ };
+
+ Children = new Drawable[]
+ {
+ overlay,
+ firedText
+ };
+
+ AddStep("start confirming", () => overlay.Begin());
+ AddStep("abort confirming", () => overlay.Abort());
+
+ AddAssert("ensure aborted", () => !fired);
+
+ AddStep("start confirming", () => overlay.Begin());
+
+ AddUntilStep(() => fired, "wait until confirmed");
+ }
+
+ private class TestHoldToConfirmOverlay : ExitConfirmOverlay
+ {
+ protected override bool AllowMultipleFires => true;
+
+ public void Begin() => BeginConfirm();
+ public void Abort() => AbortConfirm();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseLounge.cs b/osu.Game.Tests/Visual/TestCaseLounge.cs
new file mode 100644
index 0000000000..b96d705d5c
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseLounge.cs
@@ -0,0 +1,211 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Multi.Components;
+using osu.Game.Screens.Multi.Screens.Lounge;
+using osu.Game.Users;
+using OpenTK.Input;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseLounge : ManualInputManagerTestCase
+ {
+ private TestLounge lounge;
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ lounge = new TestLounge();
+
+ Room[] rooms =
+ {
+ new Room
+ {
+ Name = { Value = @"Just Another Room" },
+ Host = { Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } } },
+ Status = { Value = new RoomStatusPlaying() },
+ Availability = { Value = RoomAvailability.Public },
+ Type = { Value = new GameTypeTagTeam() },
+ Beatmap =
+ {
+ Value = new BeatmapInfo
+ {
+ StarDifficulty = 5.65,
+ Ruleset = rulesets.GetRuleset(0),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"Sidetracked Day (Short Ver.)",
+ Artist = @"VINXIS",
+ AuthorString = @"Hobbes2",
+ },
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/767600/covers/cover.jpg?1526243446",
+ },
+ },
+ },
+ }
+ },
+ MaxParticipants = { Value = 10 },
+ Participants =
+ {
+ Value = new[]
+ {
+ new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } },
+ new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } },
+ new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } },
+ new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } },
+ new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } },
+ new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } },
+ }
+ }
+ },
+ new Room
+ {
+ Name = { Value = @"Not Just Any Room" },
+ Host = { Value = new User { Username = @"Monstrata", Id = 2706438, Country = new Country { FlagName = @"CA" } } },
+ Status = { Value = new RoomStatusOpen() },
+ Availability = { Value = RoomAvailability.FriendsOnly },
+ Type = { Value = new GameTypeTeamVersus() },
+ Beatmap =
+ {
+ Value = new BeatmapInfo
+ {
+ StarDifficulty = 2.73,
+ Ruleset = rulesets.GetRuleset(0),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"lit(var)",
+ Artist = @"kensuke ushio",
+ AuthorString = @"Monstrata",
+ },
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/623972/covers/cover.jpg?1521167183",
+ },
+ },
+ },
+ }
+ },
+ Participants =
+ {
+ Value = new[]
+ {
+ new User { Username = @"Jeby", Id = 3136279, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 3497 } } },
+ new User { Username = @"DualAkira", Id = 5220933, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 643 } } },
+ new User { Username = @"Datenshi Yohane", Id = 7171857, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10555 } } },
+ }
+ }
+ },
+ new Room
+ {
+ Name = { Value = @"room THE FINAL" },
+ Host = { Value = new User { Username = @"Delis", Id = 1603923, Country = new Country { FlagName = @"JP" } } },
+ Status = { Value = new RoomStatusPlaying() },
+ Availability = { Value = RoomAvailability.Public },
+ Type = { Value = new GameTypeTagTeam() },
+ Beatmap =
+ {
+ Value = new BeatmapInfo
+ {
+ StarDifficulty = 4.48,
+ Ruleset = rulesets.GetRuleset(3),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"ONIGIRI FREEWAY",
+ Artist = @"OISHII",
+ AuthorString = @"Mentholzzz",
+ },
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/663098/covers/cover.jpg?1521898837",
+ },
+ },
+ },
+ }
+ },
+ MaxParticipants = { Value = 30 },
+ Participants =
+ {
+ Value = new[]
+ {
+ new User { Username = @"KizuA", Id = 6510442, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 5372 } } },
+ new User { Username = @"Colored", Id = 827563, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 810 } } },
+ new User { Username = @"Beryl", Id = 3817591, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10096 } } },
+ }
+ }
+ },
+ };
+
+ AddStep(@"show", () => Add(lounge));
+ AddStep(@"set rooms", () => lounge.Rooms = rooms);
+ selectAssert(0);
+ AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
+ AddAssert(@"no room selected", () => lounge.SelectedRoom == null);
+ AddStep(@"set rooms", () => lounge.Rooms = rooms);
+ selectAssert(1);
+ AddStep(@"open room 1", () => clickRoom(1));
+ AddStep(@"make lounge current", lounge.MakeCurrent);
+ filterAssert(@"THE FINAL", LoungeTab.Public, 1);
+ filterAssert(string.Empty, LoungeTab.Public, 2);
+ filterAssert(string.Empty, LoungeTab.Private, 1);
+ filterAssert(string.Empty, LoungeTab.Public, 2);
+ filterAssert(@"no matches", LoungeTab.Public, 0);
+ AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
+ AddStep(@"set rooms", () => lounge.Rooms = rooms);
+ AddAssert(@"no matches after clear", () => !lounge.ChildRooms.Any());
+ filterAssert(string.Empty, LoungeTab.Public, 2);
+ AddStep(@"exit", lounge.Exit);
+ }
+
+ private void clickRoom(int n)
+ {
+ InputManager.MoveMouseTo(lounge.ChildRooms.ElementAt(n));
+ InputManager.Click(MouseButton.Left);
+ }
+
+ private void selectAssert(int n)
+ {
+ AddStep($@"select room {n}", () => clickRoom(n));
+ AddAssert($@"room {n} selected", () => lounge.SelectedRoom == lounge.ChildRooms.ElementAt(n).Room);
+ }
+
+ private void filterAssert(string filter, LoungeTab tab, int endCount)
+ {
+ AddStep($@"filter '{filter}', {tab}", () => lounge.SetFilter(filter, tab));
+ AddAssert(@"filtered correctly", () => lounge.ChildRooms.Count() == endCount);
+ }
+
+ private class TestLounge : Lounge
+ {
+ public IEnumerable ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
+ public Room SelectedRoom => Inspector.Room;
+
+ public void SetFilter(string filter, LoungeTab tab)
+ {
+ Filter.Search.Current.Value = filter;
+ Filter.Tabs.Current.Value = tab;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index dad8fb8fed..3255478bea 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -17,6 +18,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.UI;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
@@ -24,7 +26,18 @@ namespace osu.Game.Tests.Visual
[Description("mod select and icon display")]
public class TestCaseMods : OsuTestCase
{
- private const string unranked_suffix = " (Unranked)";
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ModSelectOverlay),
+ typeof(ModDisplay),
+ typeof(ModSection),
+ typeof(ModIcon),
+ typeof(ModButton),
+ typeof(ModButtonEmpty),
+ typeof(DifficultyReductionSection),
+ typeof(DifficultyIncreaseSection),
+ typeof(SpecialSection),
+ };
private RulesetStore rulesets;
private ModDisplay modDisplay;
@@ -66,7 +79,8 @@ namespace osu.Game.Tests.Visual
Ruleset ruleset = rulesetInfo.CreateInstance();
AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo);
- switch (ruleset) {
+ switch (ruleset)
+ {
case OsuRuleset or:
testOsuMods(or);
break;
@@ -105,7 +119,7 @@ namespace osu.Game.Tests.Visual
private void testManiaMods(ManiaRuleset ruleset)
{
- testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
+ testRankedText(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
}
private void testSingleMod(Mod mod)
@@ -182,13 +196,16 @@ namespace osu.Game.Tests.Visual
checkLabelColor(Color4.White);
}
- private void testMultiplierTextUnranked(Mod mod)
+ private void testRankedText(Mod mod)
{
- AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
+ AddWaitStep(1, "wait for fade");
+ AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
selectNext(mod);
- AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
+ AddWaitStep(1, "wait for fade");
+ AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
selectPrevious(mod);
- AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
+ AddWaitStep(1, "wait for fade");
+ AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
}
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1));
@@ -224,6 +241,7 @@ namespace osu.Game.Tests.Visual
}
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
+ public new OsuSpriteText UnrankedLabel => base.UnrankedLabel;
public new TriangleButton DeselectAllButton => base.DeselectAllButton;
public new Color4 LowMultiplierColour => base.LowMultiplierColour;
diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
new file mode 100644
index 0000000000..d27a447077
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Multi;
+using osu.Game.Screens.Multi.Screens.Lounge;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMultiHeader : OsuTestCase
+ {
+ public TestCaseMultiHeader()
+ {
+ Lounge lounge;
+ Children = new Drawable[]
+ {
+ lounge = new Lounge
+ {
+ Padding = new MarginPadding { Top = Header.HEIGHT },
+ },
+ new Header(lounge),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
new file mode 100644
index 0000000000..6c22fb020f
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
@@ -0,0 +1,21 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Game.Screens.Multi;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMultiScreen : OsuTestCase
+ {
+ public TestCaseMultiScreen()
+ {
+ Multiplayer multi = new Multiplayer();
+
+ AddStep(@"show", () => Add(multi));
+ AddWaitStep(5);
+ AddStep(@"exit", multi.Exit);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
index 233c418d4a..123c1fe055 100644
--- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
+using osu.Framework.Configuration.Tracking;
+using osu.Framework.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
@@ -11,36 +13,82 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseOnScreenDisplay : OsuTestCase
{
- private FrameworkConfigManager config;
- private Bindable frameSyncMode;
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Add(new OnScreenDisplay());
-
- frameSyncMode = config.GetBindable(FrameworkSetting.FrameSync);
-
- FrameSync initial = frameSyncMode.Value;
-
- AddRepeatStep(@"Change frame limiter", setNextMode, 3);
-
- AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial);
- }
-
- private void setNextMode()
- {
- var nextMode = frameSyncMode.Value + 1;
- if (nextMode > FrameSync.Unlimited)
- nextMode = FrameSync.VSync;
- frameSyncMode.Value = nextMode;
- }
-
[BackgroundDependencyLoader]
- private void load(FrameworkConfigManager config)
+ private void load()
{
- this.config = config;
+ var config = new TestConfigManager();
+
+ var osd = new TestOnScreenDisplay();
+ osd.BeginTracking(this, config);
+ Add(osd);
+
+ AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2);
+ AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2);
+ AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3);
+ AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeybind), 3);
+ }
+
+ private class TestConfigManager : ConfigManager
+ {
+ public TestConfigManager()
+ {
+ InitialiseDefaults();
+ }
+
+ protected override void InitialiseDefaults()
+ {
+ Set(TestConfigSetting.ToggleSettingNoKeybind, false);
+ Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1);
+ Set(TestConfigSetting.ToggleSettingWithKeybind, false);
+ Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1);
+
+ base.InitialiseDefaults();
+ }
+
+ public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get(setting));
+
+ public void IncrementEnumSetting(TestConfigSetting setting)
+ {
+ var nextValue = Get(setting) + 1;
+ if (nextValue > EnumSetting.Setting4)
+ nextValue = EnumSetting.Setting1;
+ Set(setting, nextValue);
+ }
+
+ public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
+ {
+ new TrackedSetting(TestConfigSetting.ToggleSettingNoKeybind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")),
+ new TrackedSetting(TestConfigSetting.EnumSettingNoKeybind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())),
+ new TrackedSetting(TestConfigSetting.ToggleSettingWithKeybind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")),
+ new TrackedSetting(TestConfigSetting.EnumSettingWithKeybind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")),
+ };
+
+ protected override void PerformLoad()
+ {
+ }
+
+ protected override bool PerformSave() => false;
+ }
+
+ private enum TestConfigSetting
+ {
+ ToggleSettingNoKeybind,
+ EnumSettingNoKeybind,
+ ToggleSettingWithKeybind,
+ EnumSettingWithKeybind
+ }
+
+ private enum EnumSetting
+ {
+ Setting1,
+ Setting2,
+ Setting3,
+ Setting4
+ }
+
+ private class TestOnScreenDisplay : OnScreenDisplay
+ {
+ protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseQuitButton.cs b/osu.Game.Tests/Visual/TestCaseQuitButton.cs
new file mode 100644
index 0000000000..a427b7a20a
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseQuitButton.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Screens.Play.HUD;
+using OpenTK;
+using OpenTK.Input;
+
+namespace osu.Game.Tests.Visual
+{
+ [Description("'Hold to Quit' UI element")]
+ public class TestCaseQuitButton : ManualInputManagerTestCase
+ {
+ private bool exitAction;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ QuitButton quitButton;
+
+ Add(quitButton = new QuitButton
+ {
+ Origin = Anchor.BottomRight,
+ Anchor = Anchor.BottomRight,
+ Action = () => exitAction = true
+ });
+
+ var text = quitButton.Children.OfType().First();
+
+ AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton));
+ AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
+ AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
+ AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
+
+ AddStep("Trigger exit action", () =>
+ {
+ exitAction = false;
+ InputManager.MoveMouseTo(quitButton);
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ AddStep("Early release", () => InputManager.ReleaseButton(MouseButton.Left));
+ AddAssert("action not triggered", () => !exitAction);
+
+ AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
+ AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered");
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs
index 6ba671c7fc..5bc16fe420 100644
--- a/osu.Game.Tests/Visual/TestCaseReplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplay.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual
// We create a dummy RulesetContainer just to get the replay - we don't want to use mods here
// to simulate setting a replay rather than having the replay already set for us
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
- var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo));
+ var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap);
// We have the replay
var replay = dummyRulesetContainer.Replay;
diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index 88059d2dcf..06b9c4a6f9 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
{
base.LoadComplete();
- var room = new Room
+ Room room = new Room
{
Name = { Value = @"My Awesome Room" },
Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } },
@@ -71,9 +71,13 @@ namespace osu.Game.Tests.Visual
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Room = room,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
});
+ AddStep(@"set room", () => inspector.Room = room);
+ AddStep(@"null room", () => inspector.Room = null);
+ AddStep(@"set room", () => inspector.Room = room);
AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above");
AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
@@ -88,7 +92,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"change room", () =>
{
- var newRoom = new Room
+ Room newRoom = new Room
{
Name = { Value = @"My New, Better Than Ever Room" },
Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } },
diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
new file mode 100644
index 0000000000..83bbbfddd1
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -0,0 +1,142 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Screens;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens;
+using OpenTK;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseScreenBreadcrumbControl : OsuTestCase
+ {
+ private readonly ScreenBreadcrumbControl breadcrumbs;
+ private Screen currentScreen, changedScreen;
+
+ public TestCaseScreenBreadcrumbControl()
+ {
+ TestScreen startScreen;
+ OsuSpriteText titleText;
+
+ Children = new Drawable[]
+ {
+ currentScreen = startScreen = new TestScreenOne(),
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(10),
+ Children = new Drawable[]
+ {
+ breadcrumbs = new ScreenBreadcrumbControl(startScreen)
+ {
+ RelativeSizeAxes = Axes.X,
+ },
+ titleText = new OsuSpriteText(),
+ },
+ },
+ };
+
+ breadcrumbs.Current.ValueChanged += s =>
+ {
+ titleText.Text = $"Changed to {s.ToString()}";
+ changedScreen = s;
+ };
+
+ breadcrumbs.Current.TriggerChange();
+
+ assertCurrent();
+ pushNext();
+ assertCurrent();
+ pushNext();
+ assertCurrent();
+
+ AddStep(@"make start current", () =>
+ {
+ startScreen.MakeCurrent();
+ currentScreen = startScreen;
+ });
+
+ assertCurrent();
+ pushNext();
+ AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2);
+ AddStep(@"exit current", () => changedScreen.Exit());
+ AddAssert(@"current screen is first", () => startScreen == changedScreen);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ breadcrumbs.StripColour = colours.Blue;
+ }
+
+ private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
+ private void assertCurrent() => AddAssert(@"changedScreen correct", () => currentScreen == changedScreen);
+
+ private abstract class TestScreen : OsuScreen
+ {
+ protected abstract string NextTitle { get; }
+ protected abstract TestScreen CreateNextScreen();
+
+ public TestScreen PushNext()
+ {
+ TestScreen screen = CreateNextScreen();
+ Push(screen);
+
+ return screen;
+ }
+
+ protected TestScreen()
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(10),
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = Title,
+ },
+ new TriangleButton
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Width = 100,
+ Text = $"Push {NextTitle}",
+ Action = () => PushNext(),
+ },
+ },
+ };
+ }
+ }
+
+ private class TestScreenOne : TestScreen
+ {
+ public override string Title => @"Screen One";
+ protected override string NextTitle => @"Two";
+ protected override TestScreen CreateNextScreen() => new TestScreenTwo();
+ }
+
+ private class TestScreenTwo : TestScreen
+ {
+ public override string Title => @"Screen Two";
+ protected override string NextTitle => @"One";
+ protected override TestScreen CreateNextScreen() => new TestScreenOne();
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 12a017f68c..9aabb434a3 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.IO.Serialization;
using Newtonsoft.Json;
using osu.Game.IO.Serialization.Converters;
@@ -15,21 +14,27 @@ namespace osu.Game.Beatmaps
///
/// A Beatmap containing converted HitObjects.
///
- public class Beatmap : IJsonSerializable
+ public class Beatmap : IBeatmap
where T : HitObject
{
- public BeatmapInfo BeatmapInfo = new BeatmapInfo();
- public ControlPointInfo ControlPointInfo = new ControlPointInfo();
- public List Breaks = new List();
+ public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = @"Unknown",
+ Title = @"Unknown",
+ AuthorString = @"Unknown Creator",
+ },
+ Version = @"Normal",
+ BaseDifficulty = new BeatmapDifficulty()
+ };
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
- ///
- /// The HitObjects this Beatmap contains.
- ///
- [JsonConverter(typeof(TypedListConverter))]
- public List HitObjects = new List();
+ public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
+
+ public List Breaks { get; set; } = new List();
///
/// Total amount of break time in the beatmap.
@@ -38,51 +43,22 @@ namespace osu.Game.Beatmaps
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
///
- /// Constructs a new beatmap.
+ /// The HitObjects this Beatmap contains.
///
- /// The original beatmap to use the parameters of.
- public Beatmap(Beatmap original = null)
- {
- BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
- ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
- Breaks = original?.Breaks ?? Breaks;
- HitObjects = original?.HitObjects ?? HitObjects;
+ [JsonConverter(typeof(TypedListConverter))]
+ public List HitObjects = new List();
- if (original == null && Metadata == null)
- {
- // we may have no metadata in cases we weren't sourced from the database.
- // let's fill it (and other related fields) so we don't need to null-check it in future usages.
- BeatmapInfo = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata
- {
- Artist = @"Unknown",
- Title = @"Unknown",
- AuthorString = @"Unknown Creator",
- },
- Version = @"Normal",
- BaseDifficulty = new BeatmapDifficulty()
- };
- }
- }
+ IEnumerable IBeatmap.HitObjects => HitObjects;
+
+ public virtual IEnumerable GetStatistics() => Enumerable.Empty();
+
+ IBeatmap IBeatmap.Clone() => Clone();
+
+ public Beatmap Clone() => (Beatmap)MemberwiseClone();
}
- ///
- /// A Beatmap containing un-converted HitObjects.
- ///
public class Beatmap : Beatmap
{
- ///
- /// Constructs a new beatmap.
- ///
- /// The original beatmap to use the parameters of.
- public Beatmap(Beatmap original)
- : base(original)
- {
- }
-
- public Beatmap()
- {
- }
+ public new Beatmap Clone() => (Beatmap)base.Clone();
}
}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 153cace187..a1bb70135a 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -22,37 +22,37 @@ namespace osu.Game.Beatmaps
remove => ObjectConverted -= value;
}
- ///
- /// Checks if a Beatmap can be converted using this Beatmap Converter.
- ///
- /// The Beatmap to check.
- /// Whether the Beatmap can be converted using this Beatmap Converter.
- public bool CanConvert(Beatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType));
+ public IBeatmap Beatmap { get; }
- ///
- /// Converts a Beatmap using this Beatmap Converter.
- ///
- /// The un-converted Beatmap.
- /// The converted Beatmap.
- public Beatmap Convert(Beatmap original)
+ protected BeatmapConverter(IBeatmap beatmap)
{
- // We always operate on a clone of the original beatmap, to not modify it game-wide
- return ConvertBeatmap(new Beatmap(original));
+ Beatmap = beatmap;
}
- void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
+ ///
+ /// Whether can be converted by this .
+ ///
+ public bool CanConvert => !Beatmap.HitObjects.Any() || ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
+
+ ///
+ /// Converts .
+ ///
+ /// The converted Beatmap.
+ public IBeatmap Convert()
+ {
+ // We always operate on a clone of the original beatmap, to not modify it game-wide
+ return ConvertBeatmap(Beatmap.Clone());
+ }
///
/// Performs the conversion of a Beatmap using this Beatmap Converter.
///
/// The un-converted Beatmap.
/// The converted Beatmap.
- protected virtual Beatmap ConvertBeatmap(Beatmap original)
+ protected virtual Beatmap ConvertBeatmap(IBeatmap original)
{
var beatmap = CreateBeatmap();
- // todo: this *must* share logic (or directly use) Beatmap's constructor.
- // right now this isn't easily possible due to generic entanglement.
beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
@@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps
/// The hit object to convert.
/// The un-converted Beatmap.
/// The converted hit object.
- private IEnumerable convert(HitObject original, Beatmap beatmap)
+ private IEnumerable convert(HitObject original, IBeatmap beatmap)
{
// Check if the hitobject is already the converted type
T tObject = original as T;
@@ -107,6 +107,6 @@ namespace osu.Game.Beatmaps
/// The hit object to convert.
/// The un-converted Beatmap.
/// The converted hit object.
- protected abstract IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap);
+ protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap);
}
}
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 855e8fe881..508232dbfe 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps
public double SliderMultiplier { get; set; } = 1;
public double SliderTickRate { get; set; } = 1;
+ ///
+ /// Returns a shallow-clone of this .
+ ///
+ public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone();
+
///
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
///
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index a1b97afc6c..40d62103a8 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -143,5 +143,10 @@ namespace osu.Game.Beatmaps
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
+
+ ///
+ /// Returns a shallow-clone of this .
+ ///
+ public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 645e52a6c6..abc0351a82 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -9,7 +9,9 @@ using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
@@ -79,7 +81,7 @@ namespace osu.Game.Beatmaps
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
{
- model.Beatmaps = createBeatmapDifficulties(archive);
+ model.Beatmaps = createBeatmapDifficulties(model, archive);
// remove metadata from difficulties where it matches the set
foreach (BeatmapInfo b in model.Beatmaps)
@@ -105,6 +107,7 @@ namespace osu.Game.Beatmaps
{
Delete(existingOnlineId);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
+ Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
}
}
@@ -301,7 +304,7 @@ namespace osu.Game.Beatmaps
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
- if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder.");
+ if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(mapName)))
@@ -319,7 +322,7 @@ namespace osu.Game.Beatmaps
///
/// Create all required s for the provided archive.
///
- private List createBeatmapDifficulties(ArchiveReader reader)
+ private List createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
{
var beatmapInfos = new List();
@@ -333,17 +336,32 @@ namespace osu.Game.Beatmaps
ms.Position = 0;
var decoder = Decoder.GetDecoder(sr);
- Beatmap beatmap = decoder.Decode(sr);
+ IBeatmap beatmap = decoder.Decode(sr);
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
+ // ensure we have the same online set ID as the set itself.
+ beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
+ beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
+
+ // check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
+ if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
+ beatmap.BeatmapInfo.OnlineBeatmapID = null;
+
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
- // TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.Ruleset = ruleset;
- beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
+
+ if (ruleset != null)
+ {
+ // TODO: this should be done in a better place once we actually need to dynamically update it.
+ var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset);
+ beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
+ }
+ else
+ beatmap.BeatmapInfo.StarDifficulty = 0;
beatmapInfos.Add(beatmap.BeatmapInfo);
}
@@ -351,5 +369,23 @@ namespace osu.Game.Beatmaps
return beatmapInfos;
}
+
+ ///
+ /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
+ ///
+ private class DummyConversionBeatmap : WorkingBeatmap
+ {
+ private readonly IBeatmap beatmap;
+
+ public DummyConversionBeatmap(IBeatmap beatmap)
+ : base(beatmap.BeatmapInfo)
+ {
+ this.beatmap = beatmap;
+ }
+
+ protected override IBeatmap GetBeatmap() => beatmap;
+ protected override Texture GetBackground() => null;
+ protected override Track GetTrack() => null;
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 8e09d66c42..71406c6034 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
this.audioManager = audioManager;
}
- protected override Beatmap GetBeatmap()
+ protected override IBeatmap GetBeatmap()
{
try
{
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index 8f5a2a4cab..bf1cd7d4ee 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -2,30 +2,47 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
+ public interface IBeatmapProcessor
+ {
+ IBeatmap Beatmap { get; }
+
+ ///
+ /// Post-processes to add mode-specific components that aren't added during conversion.
+ ///
+ /// An example of such a usage is for combo colours.
+ ///
+ ///
+ void PostProcess();
+ }
+
///
/// Processes a post-converted Beatmap.
///
/// The type of HitObject contained in the Beatmap.
- public class BeatmapProcessor
- where TObject : HitObject
+ public class BeatmapProcessor : IBeatmapProcessor
{
+ public IBeatmap Beatmap { get; }
+
+ public BeatmapProcessor(IBeatmap beatmap)
+ {
+ Beatmap = beatmap;
+ }
+
///
/// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
///
/// An example of such a usage is for combo colours.
///
///
- /// The Beatmap to process.
- public virtual void PostProcess(Beatmap beatmap)
+ public virtual void PostProcess()
{
IHasComboInformation lastObj = null;
- foreach (var obj in beatmap.HitObjects.OfType())
+ foreach (var obj in Beatmap.HitObjects.OfType())
{
if (obj.NewCombo)
{
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
deleted file mode 100644
index 5e2d9afd23..0000000000
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ /dev/null
@@ -1,64 +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.Objects;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Mods;
-using osu.Framework.Timing;
-using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
-
-namespace osu.Game.Beatmaps
-{
- public abstract class DifficultyCalculator
- {
- protected double TimeRate = 1;
-
- public abstract double Calculate(Dictionary categoryDifficulty = null);
- }
-
- public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject
- {
- protected readonly Beatmap Beatmap;
- protected readonly Mod[] Mods;
-
- protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null)
- {
- Mods = mods ?? new Mod[0];
-
- var converter = CreateBeatmapConverter(beatmap);
-
- foreach (var mod in Mods.OfType>())
- mod.ApplyToBeatmapConverter(converter);
-
- Beatmap = converter.Convert(beatmap);
-
- ApplyMods(Mods);
-
- PreprocessHitObjects();
- }
-
- protected virtual void ApplyMods(Mod[] mods)
- {
- var clock = new StopwatchClock();
- mods.OfType().ForEach(m => m.ApplyToClock(clock));
- TimeRate = clock.Rate;
-
- foreach (var mod in Mods.OfType())
- mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
-
- foreach (var h in Beatmap.HitObjects)
- h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
-
- foreach (var mod in mods.OfType>())
- foreach (var obj in Beatmap.HitObjects)
- mod.ApplyToHitObject(obj);
- }
-
- protected virtual void PreprocessHitObjects()
- {
- }
-
- protected abstract BeatmapConverter CreateBeatmapConverter(Beatmap beatmap);
- }
-}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 0424ff84f1..8094abe5ed 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -6,7 +6,9 @@ using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
namespace osu.Game.Beatmaps
@@ -39,7 +41,7 @@ namespace osu.Game.Beatmaps
this.game = game;
}
- protected override Beatmap GetBeatmap() => new Beatmap();
+ protected override IBeatmap GetBeatmap() => new Beatmap();
protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
@@ -53,12 +55,14 @@ namespace osu.Game.Beatmaps
{
public override IEnumerable GetModsFor(ModType type) => new Mod[] { };
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset)
+ public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap)
{
throw new NotImplementedException();
}
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => null;
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
+
+ public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null;
public override string Description => "dummy";
@@ -68,6 +72,14 @@ namespace osu.Game.Beatmaps
: base(rulesetInfo)
{
}
+
+ private class DummyBeatmapConverter : IBeatmapConverter
+ {
+ public event Action> ObjectConverted;
+ public IBeatmap Beatmap { get; set; }
+ public bool CanConvert => true;
+ public IBeatmap Convert() => Beatmap;
+ }
}
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 655355913c..2aee419d20 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -54,9 +54,11 @@ namespace osu.Game.Beatmaps.Formats
base.ParseStreamInto(stream, beatmap);
- // objects may be out of order *only* if a user has manually edited an .osu file.
- // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
- this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime));
+ // Objects may be out of order *only* if a user has manually edited an .osu file.
+ // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
+ // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted)
+ // The parsing order of hitobjects matters in mania difficulty calculation
+ this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
foreach (var hitObject in this.beatmap.HitObjects)
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
new file mode 100644
index 0000000000..fe20bce98a
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -0,0 +1,56 @@
+// 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 osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Beatmaps
+{
+ public interface IBeatmap : IJsonSerializable
+ {
+ ///
+ /// This beatmap's info.
+ ///
+ BeatmapInfo BeatmapInfo { get; set; }
+
+ ///
+ /// This beatmap's metadata.
+ ///
+ BeatmapMetadata Metadata { get; }
+
+ ///
+ /// The control points in this beatmap.
+ ///
+ ControlPointInfo ControlPointInfo { get; }
+
+ ///
+ /// The breaks in this beatmap.
+ ///
+ List Breaks { get; }
+
+ ///
+ /// Total amount of break time in the beatmap.
+ ///
+ double TotalBreakTime { get; }
+
+ ///
+ /// The hitobjects contained by this beatmap.
+ ///
+ IEnumerable HitObjects { get; }
+
+ ///
+ /// Returns statistics for the contained in this beatmap.
+ ///
+ ///
+ IEnumerable GetStatistics();
+
+ ///
+ /// Creates a shallow-clone of this beatmap and returns it.
+ ///
+ /// The shallow-cloned beatmap.
+ IBeatmap Clone();
+ }
+}
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index 6c25395a56..00566093b8 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -16,10 +16,16 @@ namespace osu.Game.Beatmaps
///
event Action> ObjectConverted;
+ IBeatmap Beatmap { get; }
+
///
- /// Converts a Beatmap using this Beatmap Converter.
+ /// Whether can be converted by this .
///
- /// The un-converted Beatmap.
- void Convert(Beatmap beatmap);
+ bool CanConvert { get; }
+
+ ///
+ /// Converts .
+ ///
+ IBeatmap Convert();
}
}
diff --git a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
deleted file mode 100644
index eea82dac6d..0000000000
--- a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.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
-
-namespace osu.Game.Beatmaps.Legacy
-{
- ///
- /// A type of Beatmap loaded from a legacy .osu beatmap file (version <=15).
- ///
- public class LegacyBeatmap : Beatmap
- {
- ///
- /// Constructs a new beatmap.
- ///
- /// The original beatmap to use the parameters of.
- internal LegacyBeatmap(Beatmap original = null)
- : base(original)
- {
- HitObjects = original?.HitObjects;
- }
- }
-}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 4080e34e81..66a6206c16 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -14,6 +14,8 @@ using osu.Framework.IO.File;
using System.IO;
using osu.Game.IO.Serialization;
using System.Diagnostics;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Beatmaps
@@ -36,7 +38,7 @@ namespace osu.Game.Beatmaps
Mods.ValueChanged += mods => applyRateAdjustments();
- beatmap = new AsyncLazy(populateBeatmap);
+ beatmap = new AsyncLazy(populateBeatmap);
background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed);
track = new AsyncLazy
+ /// Whether to start a transaction for this write.
/// A usage containing a usable context.
- DatabaseWriteUsage GetForWrite();
+ DatabaseWriteUsage GetForWrite(bool withTransaction = true);
}
}
diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs
index 8569d81f01..69a1f57cc4 100644
--- a/osu.Game/Database/MutableDatabaseBackedStore.cs
+++ b/osu.Game/Database/MutableDatabaseBackedStore.cs
@@ -50,11 +50,10 @@ namespace osu.Game.Database
/// The item to update.
public void Update(T item)
{
- ItemRemoved?.Invoke(item);
-
using (var usage = ContextFactory.GetForWrite())
usage.Context.Update(item);
+ ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item);
}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
index 1979ce3648..7758b3eb25 100644
--- a/osu.Game/Database/OsuDbContext.cs
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -3,7 +3,6 @@
using System;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using osu.Framework.Logging;
@@ -83,8 +82,8 @@ namespace osu.Game.Database
base.OnModelCreating(modelBuilder);
modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique();
- modelBuilder.Entity().HasIndex(b => b.MD5Hash).IsUnique();
- modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
+ modelBuilder.Entity().HasIndex(b => b.MD5Hash);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique();
modelBuilder.Entity().HasIndex(b => b.DeletePending);
@@ -104,19 +103,6 @@ namespace osu.Game.Database
modelBuilder.Entity().HasOne(b => b.BaseDifficulty);
}
- public IDbContextTransaction BeginTransaction()
- {
- // return Database.BeginTransaction();
- return null;
- }
-
- public int SaveChanges(IDbContextTransaction transaction = null)
- {
- var ret = base.SaveChanges();
- if (ret > 0) transaction?.Commit();
- return ret;
- }
-
private class OsuDbLoggerFactory : ILoggerFactory
{
#region Disposal
diff --git a/osu.Game/Database/SingletonContextFactory.cs b/osu.Game/Database/SingletonContextFactory.cs
index 74951e8433..ce3fbf6881 100644
--- a/osu.Game/Database/SingletonContextFactory.cs
+++ b/osu.Game/Database/SingletonContextFactory.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Database
public OsuDbContext Get() => context;
- public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null);
+ public DatabaseWriteUsage GetForWrite(bool withTransaction = true) => new DatabaseWriteUsage(context, null);
}
}
diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
new file mode 100644
index 0000000000..adfc258f61
--- /dev/null
+++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Graphics.Containers
+{
+ public abstract class HoldToConfirmContainer : Container
+ {
+ public Action Action;
+
+ private const int activate_delay = 400;
+ private const int fadeout_delay = 200;
+
+ private bool fired;
+ private bool confirming;
+
+ ///
+ /// Whether the overlay should be allowed to return from a fired state.
+ ///
+ protected virtual bool AllowMultipleFires => false;
+
+ public Bindable Progress = new BindableDouble();
+
+ protected void BeginConfirm()
+ {
+ if (confirming || !AllowMultipleFires && fired) return;
+
+ confirming = true;
+
+ this.TransformBindableTo(Progress, 1, activate_delay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
+ }
+
+ protected virtual void Confirm()
+ {
+ Action?.Invoke();
+ fired = true;
+ }
+
+ protected void AbortConfirm()
+ {
+ if (!AllowMultipleFires && fired) return;
+
+ confirming = false;
+
+ this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 2a30e0d032..11a2034a8f 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
+using osu.Framework.Configuration;
namespace osu.Game.Graphics.Containers
{
@@ -15,9 +16,14 @@ namespace osu.Game.Graphics.Containers
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
- [BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private readonly BindableBool allowOpeningOverlays = new BindableBool(true);
+
+ [BackgroundDependencyLoader(true)]
+ private void load(OsuGame osuGame, AudioManager audio)
{
+ if (osuGame != null)
+ allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays);
+
samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
@@ -44,30 +50,22 @@ namespace osu.Game.Graphics.Containers
return base.OnClick(state);
}
- protected override bool OnDragStart(InputState state)
- {
- if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
- {
- State = Visibility.Hidden;
- return true;
- }
-
- return base.OnDragStart(state);
- }
-
- protected override bool OnDrag(InputState state) => State == Visibility.Hidden;
-
private void onStateChanged(Visibility visibility)
{
- switch (visibility)
+ if (allowOpeningOverlays)
{
- case Visibility.Visible:
- samplePopIn?.Play();
- break;
- case Visibility.Hidden:
- samplePopOut?.Play();
- break;
+ switch (visibility)
+ {
+ case Visibility.Visible:
+ samplePopIn?.Play();
+ break;
+ case Visibility.Hidden:
+ samplePopOut?.Play();
+ break;
+ }
}
+ else
+ State = Visibility.Hidden;
}
}
}
diff --git a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
similarity index 94%
rename from osu.Game/Graphics/Cursor/CursorOverrideContainer.cs
rename to osu.Game/Graphics/Cursor/MenuCursorContainer.cs
index 1e56cb6052..5823fad93a 100644
--- a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Cursor
///
/// A container which provides a which can be overridden by hovered s.
///
- public class CursorOverrideContainer : Container, IProvideCursor
+ public class MenuCursorContainer : Container, IProvideCursor
{
protected override Container Content => content;
private readonly Container content;
@@ -25,7 +25,7 @@ namespace osu.Game.Graphics.Cursor
public CursorContainer Cursor { get; }
public bool ProvidingUserCursor => true;
- public CursorOverrideContainer()
+ public MenuCursorContainer()
{
AddRangeInternal(new Drawable[]
{
diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index 3f59eeca97..f5017de639 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -14,14 +14,18 @@ namespace osu.Game.Graphics.UserInterface
public class BreadcrumbControl : OsuTabControl
{
private const float padding = 10;
+ private const float item_chevron_size = 10;
- protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value);
+ protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value)
+ {
+ AccentColour = AccentColour,
+ };
- protected override float StripWidth() => base.StripWidth() - (padding + 8);
+ protected override float StripWidth() => base.StripWidth() - (padding + item_chevron_size);
public BreadcrumbControl()
{
- Height = 26;
+ Height = 32;
TabContainer.Spacing = new Vector2(padding, 0f);
Current.ValueChanged += tab =>
{
@@ -47,6 +51,7 @@ namespace osu.Game.Graphics.UserInterface
public override bool HandleKeyboardInput => State == Visibility.Visible;
public override bool HandleMouseInput => State == Visibility.Visible;
+ public override bool IsRemovable => true;
private Visibility state;
@@ -77,13 +82,14 @@ namespace osu.Game.Graphics.UserInterface
public BreadcrumbTabItem(T value) : base(value)
{
- Text.TextSize = 16;
- Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width
+ Text.TextSize = 18;
+ Text.Margin = new MarginPadding { Vertical = 8 };
+ Padding = new MarginPadding { Right = padding + item_chevron_size };
Add(Chevron = new SpriteIcon
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
- Size = new Vector2(12),
+ Size = new Vector2(item_chevron_size),
Icon = FontAwesome.fa_chevron_right,
Margin = new MarginPadding { Left = padding },
Alpha = 0f,
diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
new file mode 100644
index 0000000000..77079894cc
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Diagnostics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Input;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public class ExternalLinkButton : CompositeDrawable, IHasTooltip
+ {
+ public string Link { get; set; }
+
+ private Color4 hoverColour;
+
+ public ExternalLinkButton(string link = null)
+ {
+ Link = link;
+ Size = new Vector2(12);
+ InternalChild = new SpriteIcon
+ {
+ Icon = FontAwesome.fa_external_link,
+ RelativeSizeAxes = Axes.Both
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ hoverColour = colours.Yellow;
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint);
+ base.OnHoverLost(state);
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if(Link != null)
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = Link,
+ UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361
+ });
+ return true;
+ }
+
+ public string TooltipText => "View in browser";
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index fc14a9c6ba..d015a563f6 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -6,6 +6,7 @@ using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -157,7 +158,7 @@ namespace osu.Game.Graphics.UserInterface
Margin = new MarginPadding { Top = 5, Bottom = 5 },
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
- Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+ Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(),
TextSize = 14,
Font = @"Exo2.0-Bold", // Font should only turn bold when active?
},
diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
new file mode 100644
index 0000000000..adcf401546
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -0,0 +1,54 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Screens;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ ///
+ /// A which follows the active screen (and allows navigation) in a stack.
+ ///
+ public class ScreenBreadcrumbControl : BreadcrumbControl
+ {
+ private Screen last;
+
+ public ScreenBreadcrumbControl(Screen initialScreen)
+ {
+ Current.ValueChanged += newScreen =>
+ {
+ if (last != newScreen && !newScreen.IsCurrentScreen)
+ newScreen.MakeCurrent();
+ };
+
+ onPushed(initialScreen);
+ }
+
+ private void screenChanged(Screen newScreen)
+ {
+ if (newScreen == null) return;
+
+ if (last != null)
+ {
+ last.Exited -= screenChanged;
+ last.ModePushed -= onPushed;
+ }
+
+ last = newScreen;
+
+ newScreen.Exited += screenChanged;
+ newScreen.ModePushed += onPushed;
+
+ Current.Value = newScreen;
+ }
+
+ private void onPushed(Screen screen)
+ {
+ Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem);
+ AddItem(screen);
+
+ screenChanged(screen);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/SelectionState.cs b/osu.Game/Graphics/UserInterface/SelectionState.cs
new file mode 100644
index 0000000000..079ae343eb
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/SelectionState.cs
@@ -0,0 +1,11 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public enum SelectionState
+ {
+ NotSelected,
+ Selected
+ }
+}
diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs
index c9727725ef..ce6ff7c82d 100644
--- a/osu.Game/IO/Serialization/IJsonSerializable.cs
+++ b/osu.Game/IO/Serialization/IJsonSerializable.cs
@@ -18,8 +18,6 @@ namespace osu.Game.IO.Serialization
public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings());
- public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj));
-
///
/// Creates the default that should be used for all s.
///
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index dd8f00f6cd..fced2e5d95 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,8 @@ namespace osu.Game.Input.Bindings
{
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
- new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
+ new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
+ new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
@@ -36,6 +37,9 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
+
+ new KeyBinding(InputKey.Escape, GlobalAction.Back),
+ new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
};
public IEnumerable InGameKeyBindings => new[]
@@ -76,6 +80,11 @@ namespace osu.Game.Input.Bindings
QuickRetry,
[Description("Take screenshot")]
- TakeScreenshot
+ TakeScreenshot,
+ [Description("Toggle gameplay mouse buttons")]
+ ToggleGameplayMouseButtons,
+
+ [Description("Go back")]
+ Back
}
}
diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs
new file mode 100644
index 0000000000..f28408bfb3
--- /dev/null
+++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs
@@ -0,0 +1,377 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using osu.Game.Database;
+using System;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20180529055154_RemoveUniqueHashConstraints")]
+ partial class RemoveUniqueHashConstraints
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntKey")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs
new file mode 100644
index 0000000000..fe8c983a3f
--- /dev/null
+++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs
@@ -0,0 +1,53 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+using System.Collections.Generic;
+
+namespace osu.Game.Migrations
+{
+ public partial class RemoveUniqueHashConstraints : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo");
+
+ migrationBuilder.DropIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo",
+ column: "Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo",
+ column: "MD5Hash");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo");
+
+ migrationBuilder.DropIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo",
+ column: "Hash",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo",
+ column: "MD5Hash",
+ unique: true);
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 2abbe7785f..d750d50b5b 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -1,7 +1,11 @@
//
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database;
+using System;
namespace osu.Game.Migrations
{
@@ -12,7 +16,7 @@ namespace osu.Game.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+ .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
@@ -27,9 +31,9 @@ namespace osu.Game.Migrations
b.Property("OverallDifficulty");
- b.Property("SliderMultiplier");
+ b.Property("SliderMultiplier");
- b.Property("SliderTickRate");
+ b.Property("SliderTickRate");
b.HasKey("ID");
@@ -91,11 +95,9 @@ namespace osu.Game.Migrations
b.HasIndex("BeatmapSetInfoID");
- b.HasIndex("Hash")
- .IsUnique();
+ b.HasIndex("Hash");
- b.HasIndex("MD5Hash")
- .IsUnique();
+ b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index f1c23e9e84..ae3fb5ec6e 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -12,6 +12,7 @@ namespace osu.Game.Online.Multiplayer
public Bindable Name = new Bindable();
public Bindable Host = new Bindable();
public Bindable Status = new Bindable();
+ public Bindable Availability = new Bindable();
public Bindable Type = new Bindable();
public Bindable Beatmap = new Bindable();
public Bindable MaxParticipants = new Bindable();
diff --git a/osu.Game/Online/Multiplayer/RoomAvailability.cs b/osu.Game/Online/Multiplayer/RoomAvailability.cs
new file mode 100644
index 0000000000..6c154207ff
--- /dev/null
+++ b/osu.Game/Online/Multiplayer/RoomAvailability.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.ComponentModel;
+
+namespace osu.Game.Online.Multiplayer
+{
+ public enum RoomAvailability
+ {
+ Public,
+
+ [Description(@"Friends Only")]
+ FriendsOnly,
+
+ [Description(@"Invite Only")]
+ InviteOnly,
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 5cf2cf9b4a..b7bf9cb993 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -77,7 +77,8 @@ namespace osu.Game
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
- public readonly BindableBool ShowOverlays = new BindableBool();
+ public readonly BindableBool HideOverlaysOnEnter = new BindableBool();
+ public readonly BindableBool AllowOpeningOverlays = new BindableBool(true);
private OsuScreen screenStack;
@@ -205,7 +206,7 @@ namespace osu.Game
protected override void LoadComplete()
{
- // this needs to be cached before base.LoadComplete as it is used by CursorOverrideContainer.
+ // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer.
dependencies.Cache(screenshotManager = new ScreenshotManager());
base.LoadComplete();
@@ -213,7 +214,7 @@ namespace osu.Game
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
// in the cursor being shown for a few frames during the intro.
// This prevents the cursor from showing until we have a screen with CursorVisible = true
- CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
+ MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
// hook up notifications to components.
SkinManager.PostNotification = n => notifications?.Post(n);
@@ -361,12 +362,12 @@ namespace osu.Game
settings.StateChanged += _ => updateScreenOffset();
notifications.StateChanged += _ => updateScreenOffset();
- notifications.Enabled.BindTo(ShowOverlays);
+ notifications.Enabled.BindTo(AllowOpeningOverlays);
- ShowOverlays.ValueChanged += show =>
+ HideOverlaysOnEnter.ValueChanged += hide =>
{
//central game screen change logic.
- if (!show)
+ if (hide)
{
hideAllOverlays();
musicController.State = Visibility.Hidden;
@@ -460,6 +461,9 @@ namespace osu.Game
case GlobalAction.ToggleDirect:
direct.ToggleVisibility();
return true;
+ case GlobalAction.ToggleGameplayMouseButtons:
+ LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons));
+ return true;
}
return false;
@@ -538,7 +542,7 @@ namespace osu.Game
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
- CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
+ MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
}
private void screenAdded(Screen newScreen)
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 6d1b2dfda2..1360bbfdce 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -58,7 +58,7 @@ namespace osu.Game
protected SettingsStore SettingsStore;
- protected CursorOverrideContainer CursorOverrideContainer;
+ protected MenuCursorContainer MenuCursorContainer;
protected override string MainResourceFile => @"osu.Game.Resources.dll";
@@ -196,14 +196,14 @@ namespace osu.Game
GlobalActionContainer globalBinding;
- CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both };
- CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this)
+ MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
+ MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this)
{
RelativeSizeAxes = Axes.Both,
- Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both }
+ Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
};
- base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer });
+ base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer });
KeyBindingStore.Register(globalBinding);
dependencies.Cache(globalBinding);
@@ -213,7 +213,7 @@ namespace osu.Game
{
try
{
- using (var db = contextFactory.GetForWrite())
+ using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate();
}
catch (MigrationFailedException e)
@@ -225,7 +225,7 @@ namespace osu.Game
contextFactory.ResetDatabase();
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
- using (var db = contextFactory.GetForWrite())
+ using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate();
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index 9b25d61f58..8833a89479 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.BeatmapSet.Buttons;
using OpenTK;
using OpenTK.Graphics;
@@ -93,6 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet
public Header()
{
+ ExternalLinkButton externalLink;
RelativeSizeAxes = Axes.X;
Height = 400;
Masking = true;
@@ -160,10 +162,24 @@ namespace osu.Game.Overlays.BeatmapSet
Height = 113,
Child = Picker = new BeatmapPicker(),
},
- title = new OsuSpriteText
+ new FillFlowContainer
{
- Font = @"Exo2.0-BoldItalic",
- TextSize = 37,
+ Direction = FillDirection.Horizontal,
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ title = new OsuSpriteText
+ {
+ Font = @"Exo2.0-BoldItalic",
+ TextSize = 37,
+ },
+ externalLink = new ExternalLinkButton
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Left = 3, Bottom = 4 }, //To better lineup with the font
+ },
+ }
},
artist = new OsuSpriteText
{
@@ -247,6 +263,7 @@ namespace osu.Game.Overlays.BeatmapSet
};
Picker.Beatmap.ValueChanged += b => Details.Beatmap = b;
+ Picker.Beatmap.ValueChanged += b => externalLink.Link = $@"https://osu.ppy.sh/beatmapsets/{BeatmapSet?.OnlineBeatmapSetID}#{b?.Ruleset.ShortName}/{b?.OnlineBeatmapID}";
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 4d13e05c9e..438277bfe6 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -11,10 +11,8 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Transforms;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -174,7 +172,7 @@ namespace osu.Game.Overlays
{
textbox.HoldFocus = false;
if (1f - ChatHeight.Value < channel_selection_min_height)
- transformChatHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint);
+ this.TransformBindableTo(ChatHeight, 1f - channel_selection_min_height, 800, Easing.OutQuint);
}
else
textbox.HoldFocus = true;
@@ -362,26 +360,5 @@ namespace osu.Game.Overlays
textbox.Text = string.Empty;
}
-
- private void transformChatHeightTo(double newChatHeight, double duration = 0, Easing easing = Easing.None)
- {
- this.TransformTo(this.PopulateTransform(new TransformChatHeight(), newChatHeight, duration, easing));
- }
-
- private class TransformChatHeight : Transform
- {
- private double valueAt(double time)
- {
- if (time < StartTime) return StartValue;
- if (time >= EndTime) return EndValue;
-
- return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
- }
-
- public override string TargetMember => "ChatHeight.Value";
-
- protected override void Apply(ChatOverlay d, double time) => d.ChatHeight.Value = valueAt(time);
- protected override void ReadIntoStartValue(ChatOverlay d) => StartValue = d.ChatHeight.Value;
- }
}
}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 8883dfdebb..4f4348c131 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -1,8 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK;
-using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
@@ -12,6 +10,8 @@ using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets;
+using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct
{
@@ -22,6 +22,7 @@ namespace osu.Game.Overlays.Direct
protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552");
protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked;
+
protected override Drawable CreateSupplementaryControls()
{
modeButtons = new FillFlowContainer
@@ -38,7 +39,7 @@ namespace osu.Game.Overlays.Direct
{
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
- Ruleset.BindTo(game?.Ruleset ?? new Bindable { Value = rulesets.GetRuleset(0) });
+ Ruleset.Value = game?.Ruleset.Value ?? rulesets.GetRuleset(0);
foreach (var r in rulesets.AvailableRulesets)
{
modeButtons.Add(new RulesetToggleButton(Ruleset, r));
@@ -49,14 +50,15 @@ namespace osu.Game.Overlays.Direct
{
private Drawable icon
{
- get { return iconContainer.Icon; }
- set { iconContainer.Icon = value; }
+ get => iconContainer.Icon;
+ set => iconContainer.Icon = value;
}
private RulesetInfo ruleset;
+
public RulesetInfo Ruleset
{
- get { return ruleset; }
+ get => ruleset;
set
{
ruleset = value;
@@ -73,6 +75,9 @@ namespace osu.Game.Overlays.Direct
iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100);
}
+ public override bool HandleKeyboardInput => !bindable.Disabled && base.HandleKeyboardInput;
+ public override bool HandleMouseInput => !bindable.Disabled && base.HandleMouseInput;
+
public RulesetToggleButton(Bindable bindable, RulesetInfo ruleset)
{
this.bindable = bindable;
diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs
new file mode 100644
index 0000000000..7e2f6f5891
--- /dev/null
+++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs
@@ -0,0 +1,39 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.Containers;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays
+{
+ ///
+ /// An overlay which will display a black screen that dims over a period before confirming an exit action.
+ /// Action is BYO (derived class will need to call and from a user event).
+ ///
+ public abstract class HoldToConfirmOverlay : HoldToConfirmContainer
+ {
+ private Box overlay;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+ AlwaysPresent = true;
+
+ Children = new Drawable[]
+ {
+ overlay = new Box
+ {
+ Alpha = 0,
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+
+ Progress.ValueChanged += v => overlay.Alpha = (float)v;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index e80a469f91..7406a9ec4f 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input;
using OpenTK.Graphics;
@@ -43,7 +44,7 @@ namespace osu.Game.Overlays.KeyBinding
}
private OsuSpriteText text;
- private OsuSpriteText pressAKey;
+ private OsuTextFlowContainer pressAKey;
private FillFlowContainer buttons;
@@ -95,10 +96,11 @@ namespace osu.Game.Overlays.KeyBinding
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
},
- pressAKey = new OsuSpriteText
+ pressAKey = new OsuTextFlowContainer
{
- Text = "Press a key to change binding, DEL to delete, ESC to cancel.",
- Y = height,
+ Text = "Press a key to change binding, Shift+Delete to delete, Escape to cancel.",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
Margin = new MarginPadding(padding),
Alpha = 0,
Colour = colours.YellowDark
@@ -204,9 +206,16 @@ namespace osu.Game.Overlays.KeyBinding
finalise();
return true;
case Key.Delete:
- bindTarget.UpdateKeyCombination(InputKey.None);
- finalise();
- return true;
+ {
+ if (state.Keyboard.ShiftPressed)
+ {
+ bindTarget.UpdateKeyCombination(InputKey.None);
+ finalise();
+ return true;
+ }
+
+ break;
+ }
}
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
@@ -223,6 +232,26 @@ namespace osu.Game.Overlays.KeyBinding
return true;
}
+ protected override bool OnJoystickPress(InputState state, Framework.Input.JoystickEventArgs args)
+ {
+ if (!HasFocus)
+ return false;
+
+ bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
+ finalise();
+
+ return true;
+ }
+
+ protected override bool OnJoystickRelease(InputState state, Framework.Input.JoystickEventArgs args)
+ {
+ if (!HasFocus)
+ return base.OnJoystickRelease(state, args);
+
+ finalise();
+ return true;
+ }
+
private void finalise()
{
if (bindTarget != null)
@@ -241,7 +270,7 @@ namespace osu.Game.Overlays.KeyBinding
GetContainingInputManager().ChangeFocus(null);
pressAKey.FadeOut(300, Easing.OutQuint);
- pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight };
+ pressAKey.Padding = new MarginPadding { Top = height, Bottom = -pressAKey.DrawHeight };
}
protected override void OnFocus(InputState state)
@@ -250,7 +279,7 @@ namespace osu.Game.Overlays.KeyBinding
AutoSizeEasing = Easing.OutQuint;
pressAKey.FadeIn(300, Easing.OutQuint);
- pressAKey.Padding = new MarginPadding();
+ pressAKey.Padding = new MarginPadding { Top = height };
updateBindTarget();
base.OnFocus(state);
diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs
index 09b5be6a85..99a86f19a1 100644
--- a/osu.Game/Overlays/MainSettings.cs
+++ b/osu.Game/Overlays/MainSettings.cs
@@ -6,9 +6,11 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Screens.Ranking;
@@ -96,7 +98,7 @@ namespace osu.Game.Overlays
});
}
- private class BackButton : OsuClickableContainer
+ private class BackButton : OsuClickableContainer, IKeyBindingHandler
{
private AspectContainer aspect;
@@ -146,6 +148,20 @@ namespace osu.Game.Overlays
aspect.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Back:
+ TriggerOnClick();
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => false;
}
}
}
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 2a4f243606..f4e0e3db04 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -116,6 +116,7 @@ namespace osu.Game.Overlays.Mods
}
private Mod mod;
+ private readonly Container scaleContainer;
public Mod Mod
{
@@ -149,14 +150,26 @@ namespace osu.Game.Overlays.Mods
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
- switch (args.Button)
+ scaleContainer.ScaleTo(0.9f, 800, Easing.Out);
+ return base.OnMouseDown(state, args);
+ }
+
+ protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+ {
+ scaleContainer.ScaleTo(1, 500, Easing.OutElastic);
+
+ // only trigger the event if we are inside the area of the button
+ if (Contains(ToScreenSpace(state.Mouse.Position - Position)))
{
- case MouseButton.Left:
- SelectNext(1);
- break;
- case MouseButton.Right:
- SelectNext(-1);
- break;
+ switch (args.Button)
+ {
+ case MouseButton.Left:
+ SelectNext(1);
+ break;
+ case MouseButton.Right:
+ SelectNext(-1);
+ break;
+ }
}
return true;
@@ -176,7 +189,8 @@ namespace osu.Game.Overlays.Mods
start = Mods.Length - 1;
for (int i = start; i < Mods.Length && i >= 0; i += direction)
- if (SelectAt(i)) return;
+ if (SelectAt(i))
+ return;
Deselect();
}
@@ -242,8 +256,14 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre,
Children = new Drawable[]
{
- iconsContainer = new Container
+ scaleContainer = new Container
{
+ Child = iconsContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ },
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 632c00d1fd..f1624721da 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Mods
protected Color4 LowMultiplierColour, HighMultiplierColour;
protected readonly TriangleButton DeselectAllButton;
- protected readonly OsuSpriteText MultiplierLabel;
+ protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel;
private readonly FillFlowContainer footerContainer;
protected override bool BlockPassThroughKeyboard => false;
@@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Mods
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
+ UnrankedLabel.Colour = colours.Blue;
if (osu != null)
Ruleset.BindTo(osu.Ruleset);
@@ -99,15 +100,14 @@ namespace osu.Game.Overlays.Mods
}
MultiplierLabel.Text = $"{multiplier:N2}x";
- if (!ranked)
- MultiplierLabel.Text += " (Unranked)";
-
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
+
+ UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
}
protected override void PopOut()
@@ -352,23 +352,33 @@ namespace osu.Game.Overlays.Mods
},
new OsuSpriteText
{
- Text = @"Score Multiplier: ",
+ Text = @"Score Multiplier:",
TextSize = 30,
- Shadow = true,
Margin = new MarginPadding
{
- Top = 5
+ Top = 5,
+ Right = 10
}
},
MultiplierLabel = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
TextSize = 30,
- Shadow = true,
Margin = new MarginPadding
{
Top = 5
}
+ },
+ UnrankedLabel = new OsuSpriteText
+ {
+ Font = @"Exo2.0-Bold",
+ Text = @"(Unranked)",
+ TextSize = 30,
+ Margin = new MarginPadding
+ {
+ Top = 5,
+ Left = 10
+ }
}
}
}
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index d63babf3b6..d4bc8c5b27 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -4,7 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Music
{
public class PlaylistList : CompositeDrawable
{
- public Action OnSelect;
+ public Action Selected;
+ public Action OrderChanged;
private readonly ItemsScrollContainer items;
@@ -25,7 +27,8 @@ namespace osu.Game.Overlays.Music
InternalChild = items = new ItemsScrollContainer
{
RelativeSizeAxes = Axes.Both,
- OnSelect = set => OnSelect?.Invoke(set)
+ Selected = set => Selected?.Invoke(set),
+ OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
};
}
@@ -35,34 +38,20 @@ namespace osu.Game.Overlays.Music
set { base.Padding = value; }
}
- public IEnumerable BeatmapSets
- {
- get { return items.Sets; }
- set { items.Sets = value; }
- }
-
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
- public BeatmapSetInfo NextSet => items.NextSet;
- public BeatmapSetInfo PreviousSet => items.PreviousSet;
-
- public BeatmapSetInfo SelectedSet
- {
- get { return items.SelectedSet; }
- set { items.SelectedSet = value; }
- }
-
- public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
- public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
private class ItemsScrollContainer : OsuScrollContainer
{
- public Action OnSelect;
+ public Action Selected;
+ public Action OrderChanged;
private readonly SearchContainer search;
private readonly FillFlowContainer items;
+ private readonly IBindable beatmapBacking = new Bindable();
+
public ItemsScrollContainer()
{
Children = new Drawable[]
@@ -83,14 +72,36 @@ namespace osu.Game.Overlays.Music
};
}
- public IEnumerable Sets
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps, OsuGameBase osuGame)
{
- get { return items.Select(x => x.BeatmapSetInfo).ToList(); }
- set
- {
- items.Clear();
- value.ForEach(AddBeatmapSet);
- }
+ beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
+ beatmaps.ItemAdded += addBeatmapSet;
+ beatmaps.ItemRemoved += removeBeatmapSet;
+
+ beatmapBacking.BindTo(osuGame.Beatmap);
+ beatmapBacking.ValueChanged += _ => updateSelectedSet();
+ }
+
+ private void addBeatmapSet(BeatmapSetInfo obj)
+ {
+ var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) };
+
+ items.Add(newItem);
+ items.SetLayoutPosition(newItem, items.Count - 1);
+ }
+
+ private void removeBeatmapSet(BeatmapSetInfo obj)
+ {
+ var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
+ if (itemToRemove != null)
+ items.Remove(itemToRemove);
+ }
+
+ private void updateSelectedSet()
+ {
+ foreach (PlaylistItem s in items.Children)
+ s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID;
}
public string SearchTerm
@@ -99,34 +110,7 @@ namespace osu.Game.Overlays.Music
set { search.SearchTerm = value; }
}
- public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
- {
- var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) };
-
- items.Add(newItem);
- items.SetLayoutPosition(newItem, items.Count);
- }
-
- public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
- {
- var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
- if (itemToRemove != null)
- items.Remove(itemToRemove);
- }
-
- public BeatmapSetInfo SelectedSet
- {
- get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
- set
- {
- foreach (PlaylistItem s in items.Children)
- s.Selected = s.BeatmapSetInfo.ID == value?.ID;
- }
- }
-
public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
- public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo;
- public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo;
private Vector2 nativeDragPosition;
private PlaylistItem draggedItem;
@@ -227,6 +211,7 @@ namespace osu.Game.Overlays.Music
}
items.SetLayoutPosition(draggedItem, dstIndex);
+ OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
}
private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index 3496c044fb..085a44ecba 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -1,7 +1,7 @@
// 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;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
@@ -19,18 +19,16 @@ namespace osu.Game.Overlays.Music
public class PlaylistOverlay : OverlayContainer
{
private const float transition_duration = 600;
-
private const float playlist_height = 510;
+ public Action OrderChanged;
+
+ private BeatmapManager beatmaps;
private FilterControl filter;
private PlaylistList list;
- private BeatmapManager beatmaps;
-
private readonly Bindable beatmapBacking = new Bindable();
- public IEnumerable BeatmapSets => list.BeatmapSets;
-
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
{
@@ -60,7 +58,8 @@ namespace osu.Game.Overlays.Music
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
- OnSelect = itemSelected,
+ Selected = itemSelected,
+ OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
},
filter = new FilterControl
{
@@ -74,30 +73,19 @@ namespace osu.Game.Overlays.Music
},
};
- beatmaps.ItemAdded += handleBeatmapAdded;
- beatmaps.ItemRemoved += handleBeatmapRemoved;
-
- list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
-
beatmapBacking.BindTo(game.Beatmap);
filter.Search.OnCommit = (sender, newText) =>
{
- var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
- if (beatmap != null) playSpecified(beatmap);
+ BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
+ if (beatmap != null)
+ {
+ beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap);
+ beatmapBacking.Value.Track.Restart();
+ }
};
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo;
- beatmapBacking.TriggerChange();
- }
-
- private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo));
- private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo));
-
protected override void PopIn()
{
filter.Search.HoldFocus = true;
@@ -123,49 +111,8 @@ namespace osu.Game.Overlays.Music
return;
}
- playSpecified(set.Beatmaps.First());
- }
-
- public void PlayPrevious()
- {
- var playable = list.PreviousSet;
-
- if (playable != null)
- {
- playSpecified(playable.Beatmaps.First());
- list.SelectedSet = playable;
- }
- }
-
- public void PlayNext()
- {
- var playable = list.NextSet;
-
- if (playable != null)
- {
- playSpecified(playable.Beatmaps.First());
- list.SelectedSet = playable;
- }
- }
-
- private void playSpecified(BeatmapInfo info)
- {
- beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
-
- var track = beatmapBacking.Value.Track;
-
- track.Restart();
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmaps != null)
- {
- beatmaps.ItemAdded -= handleBeatmapAdded;
- beatmaps.ItemRemoved -= handleBeatmapRemoved;
- }
+ beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First());
+ beatmapBacking.Value.Track.Restart();
}
}
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index b4021f2808..fb4e278b0c 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.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 System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -50,7 +51,10 @@ namespace osu.Game.Overlays
private LocalisationEngine localisation;
+ private BeatmapManager beatmaps;
private readonly Bindable beatmapBacking = new Bindable();
+ private List beatmapSets;
+ private BeatmapSetInfo currentSet;
private Container dragContainer;
private Container playerContainer;
@@ -93,8 +97,9 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
- private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation)
+ private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
{
+ this.beatmaps = beatmaps;
this.localisation = localisation;
Children = new Drawable[]
@@ -111,6 +116,7 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
+ OrderChanged = playlistOrderChanged
},
playerContainer = new Container
{
@@ -185,7 +191,7 @@ namespace osu.Game.Overlays
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Action = next,
+ Action = () => next(),
Icon = FontAwesome.fa_step_forward,
},
}
@@ -214,11 +220,24 @@ namespace osu.Game.Overlays
}
};
+ beatmapSets = beatmaps.GetAllUsableBeatmapSets();
+ beatmaps.ItemAdded += handleBeatmapAdded;
+ beatmaps.ItemRemoved += handleBeatmapRemoved;
+
beatmapBacking.BindTo(game.Beatmap);
playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
+ private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
+ {
+ beatmapSets.Remove(beatmapSetInfo);
+ beatmapSets.Insert(index, beatmapSetInfo);
+ }
+
+ private void handleBeatmapAdded(BeatmapSetInfo obj) => beatmapSets.Add(obj);
+ private void handleBeatmapRemoved(BeatmapSetInfo obj) => beatmapSets.RemoveAll(s => s.ID == obj.ID);
+
protected override void LoadComplete()
{
beatmapBacking.ValueChanged += beatmapChanged;
@@ -257,7 +276,7 @@ namespace osu.Game.Overlays
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
- if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
+ if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any())
next();
}
else
@@ -271,7 +290,7 @@ namespace osu.Game.Overlays
if (track == null)
{
if (!beatmapBacking.Disabled)
- playlist.PlayNext();
+ next(true);
return;
}
@@ -284,13 +303,26 @@ namespace osu.Game.Overlays
private void prev()
{
queuedDirection = TransformDirection.Prev;
- playlist.PlayPrevious();
+
+ var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
+ if (playable != null)
+ {
+ beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
+ beatmapBacking.Value.Track.Restart();
+ }
}
- private void next()
+ private void next(bool instant = false)
{
- queuedDirection = TransformDirection.Next;
- playlist.PlayNext();
+ if (!instant)
+ queuedDirection = TransformDirection.Next;
+
+ var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
+ if (playable != null)
+ {
+ beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
+ beatmapBacking.Value.Track.Restart();
+ }
}
private WorkingBeatmap current;
@@ -314,8 +346,8 @@ namespace osu.Game.Overlays
else
{
//figure out the best direction based on order in playlist.
- var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
- var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
+ var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
+ var next = beatmap == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
}
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 3dd088891d..1c80f2e626 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -14,6 +14,7 @@ using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays
@@ -30,6 +31,7 @@ namespace osu.Game.Overlays
private readonly SpriteText textLine3;
private const float height = 110;
+ private const float height_notext = 98;
private const float height_contracted = height * 0.9f;
private readonly FillFlowContainer optionLights;
@@ -101,12 +103,12 @@ namespace osu.Game.Overlays
},
textLine3 = new OsuSpriteText
{
- Padding = new MarginPadding { Bottom = 15 },
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Margin = new MarginPadding { Bottom = 15 },
Font = @"Exo2.0-Bold",
TextSize = 12,
Alpha = 0.3f,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
},
}
}
@@ -116,9 +118,10 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
- private void load(FrameworkConfigManager frameworkConfig)
+ private void load(FrameworkConfigManager frameworkConfig, OsuConfigManager osuConfig)
{
BeginTracking(this, frameworkConfig);
+ BeginTracking(this, osuConfig);
}
private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>();
@@ -175,13 +178,10 @@ namespace osu.Game.Overlays
textLine2.Text = description.Value;
textLine3.Text = description.Shortcut.ToUpper();
- box.Animate(
- b => b.FadeIn(500, Easing.OutQuint),
- b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
- ).Then(
- b => b.FadeOutFromOne(1500, Easing.InQuint),
- b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)
- );
+ if (string.IsNullOrEmpty(textLine3.Text))
+ textLine3.Text = "NO KEY BOUND";
+
+ Display(box);
int optionCount = 0;
int selectedOption = -1;
@@ -213,6 +213,17 @@ namespace osu.Game.Overlays
});
}
+ protected virtual void Display(Drawable toDisplay)
+ {
+ toDisplay.Animate(
+ b => b.FadeIn(500, Easing.OutQuint),
+ b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
+ ).Then(
+ b => b.FadeOutFromOne(1500, Easing.InQuint),
+ b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)
+ );
+ }
+
private class OptionLight : Container
{
private Color4 glowingColour, idleColour;
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 4c411b3210..067144c26e 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Diagnostics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
@@ -10,13 +9,13 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Profile.Header;
using osu.Game.Users;
@@ -105,11 +104,28 @@ namespace osu.Game.Overlays.Profile
Y = -75,
Size = new Vector2(25, 25)
},
- new ProfileLink(user)
+ new FillFlowContainer
{
+ Direction = FillDirection.Horizontal,
+ AutoSizeAxes = Axes.Both,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Y = -48,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = user.Username,
+ Font = @"Exo2.0-RegularItalic",
+ TextSize = 30,
+ },
+ new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}")
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font
+ },
+ }
},
countryFlag = new DrawableFlag(user.Country)
{
@@ -455,28 +471,6 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine();
}
- private class ProfileLink : OsuHoverContainer, IHasTooltip
- {
- public string TooltipText => "View Profile in Browser";
-
- public override bool HandleMouseInput => true;
-
- public ProfileLink(User user)
- {
- Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}");
-
- AutoSizeAxes = Axes.Both;
-
- Child = new OsuSpriteText
- {
- Text = user.Username,
- Font = @"Exo2.0-RegularItalic",
- TextSize = 30,
- };
- }
- }
-
-
private class GradeBadge : Container
{
private const float width = 50;
diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 05866f7002..3078c44844 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -6,7 +6,9 @@ using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
using OpenTK;
+using OpenTK.Input;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Shapes;
@@ -22,6 +24,7 @@ namespace osu.Game.Overlays.Toolbar
private readonly Drawable modeButtonLine;
private ToolbarModeButton activeButton;
+ private RulesetStore rulesets;
private readonly Bindable ruleset = new Bindable();
public ToolbarModeSelector()
@@ -67,28 +70,44 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader(true)]
private void load(RulesetStore rulesets, OsuGame game)
{
+ this.rulesets = rulesets;
foreach (var r in rulesets.AvailableRulesets)
{
modeButtons.Add(new ToolbarModeButton
{
Ruleset = r,
- Action = delegate
- {
- ruleset.Value = r;
- }
+ Action = delegate { ruleset.Value = r; }
});
}
ruleset.ValueChanged += rulesetChanged;
ruleset.DisabledChanged += disabledChanged;
+
if (game != null)
ruleset.BindTo(game.Ruleset);
else
ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
}
- public override bool HandleKeyboardInput => !ruleset.Disabled;
- public override bool HandleMouseInput => !ruleset.Disabled;
+ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ {
+ base.OnKeyDown(state, args);
+
+ if (state.Keyboard.ControlPressed && !args.Repeat && args.Key >= Key.Number1 && args.Key <= Key.Number9)
+ {
+ int requested = args.Key - Key.Number1;
+
+ RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault();
+ if (found != null)
+ ruleset.Value = found;
+ return true;
+ }
+
+ return false;
+ }
+
+ public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput;
+ public override bool HandleMouseInput => !ruleset.Disabled && base.HandleMouseInput;
private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300);
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index da63495fec..f922c507f7 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -100,6 +100,8 @@ namespace osu.Game.Overlays
public bool Adjust(GlobalAction action)
{
+ if (!IsLoaded) return false;
+
switch (action)
{
case GlobalAction.DecreaseVolume:
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
new file mode 100644
index 0000000000..070bc7ddb0
--- /dev/null
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -0,0 +1,41 @@
+// 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 osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Difficulty
+{
+ public abstract class DifficultyCalculator
+ {
+ protected readonly IBeatmap Beatmap;
+ protected readonly Mod[] Mods;
+
+ protected double TimeRate { get; private set; } = 1;
+
+ protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
+ {
+ Beatmap = beatmap;
+ Mods = mods ?? new Mod[0];
+
+ ApplyMods(Mods);
+ }
+
+ protected virtual void ApplyMods(Mod[] mods)
+ {
+ var clock = new StopwatchClock();
+ mods.OfType().ForEach(m => m.ApplyToClock(clock));
+ TimeRate = clock.Rate;
+ }
+
+ protected virtual void PreprocessHitObjects()
+ {
+ }
+
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
similarity index 52%
rename from osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
rename to osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
index 0f115fff6b..07d9c80061 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
@@ -3,41 +3,44 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Scoring
+namespace osu.Game.Rulesets.Difficulty
{
public abstract class PerformanceCalculator
- {
- public abstract double Calculate(Dictionary categoryDifficulty = null);
- }
-
- public abstract class PerformanceCalculator : PerformanceCalculator
- where TObject : HitObject
{
private readonly Dictionary attributes = new Dictionary();
protected IDictionary Attributes => attributes;
- protected readonly Beatmap Beatmap;
+ protected readonly Ruleset Ruleset;
+ protected readonly IBeatmap Beatmap;
protected readonly Score Score;
- protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+ protected double TimeRate { get; private set; } = 1;
+
+ protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
{
+ Ruleset = ruleset;
+ Beatmap = beatmap;
Score = score;
- var converter = CreateBeatmapConverter();
-
- foreach (var mod in score.Mods.OfType>())
- mod.ApplyToBeatmapConverter(converter);
-
- Beatmap = converter.Convert(beatmap);
-
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
diffCalc.Calculate(attributes);
+
+ ApplyMods(score.Mods);
}
- protected abstract BeatmapConverter CreateBeatmapConverter();
+ protected virtual void ApplyMods(Mod[] mods)
+ {
+ var clock = new StopwatchClock();
+ mods.OfType().ForEach(m => m.ApplyToClock(clock));
+ TimeRate = clock.Rate;
+ }
+
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
}
}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 1820053d3d..5f1b9a6bad 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Edit
private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
- protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
+ protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap);
protected abstract IReadOnlyList CompositionTools { get; }
diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs
index ad7c27ad80..61fb700dd3 100644
--- a/osu.Game/Rulesets/Edit/HitObjectMask.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs
@@ -6,6 +6,7 @@ using osu.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
@@ -137,10 +138,4 @@ namespace osu.Game.Rulesets.Edit
///
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
}
-
- public enum SelectionState
- {
- NotSelected,
- Selected
- }
}
diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
index a03a003810..1b8e62b53c 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
@@ -10,13 +10,12 @@ namespace osu.Game.Rulesets.Mods
/// Interface for a that applies changes to a .
///
/// The type of converted .
- public interface IApplicableToBeatmapConverter : IApplicableMod
- where TObject : HitObject
+ public interface IApplicableToBeatmapConverter : IApplicableMod
{
///
/// Applies this to a .
///
/// The to apply to.
- void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter);
+ void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter);
}
}
diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
index 0fd2e398c8..d6f330d9df 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
@@ -8,13 +8,12 @@ namespace osu.Game.Rulesets.Mods
///
/// An interface for s that can be applied to s.
///
- public interface IApplicableToHitObject : IApplicableMod
- where TObject : HitObject
+ public interface IApplicableToHitObject : IApplicableMod
{
///
/// Applies this to a .
///
/// The to apply to.
- void ApplyToHitObject(TObject hitObject);
+ void ApplyToHitObject(HitObject hitObject);
}
}
diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs
index 75f37b993d..7058d1bed6 100644
--- a/osu.Game/Rulesets/Mods/ModAutoplay.cs
+++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
public override string ShortenedName => "AT";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto;
public override string Description => "Watch a perfect automated play through the song.";
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
public bool AllowFail => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
}
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 090bc737dd..6379be9bfc 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mods
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 1.4f;
- difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio.
+ difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f);
- difficulty.DrainRate *= ratio;
- difficulty.OverallDifficulty *= ratio;
+ difficulty.DrainRate = Math.Min(difficulty.DrainRate * ratio, 10.0f);
+ difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ratio, 10.0f);
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs
index e4137254e6..0bd9becd78 100644
--- a/osu.Game/Rulesets/Mods/ModRelax.cs
+++ b/osu.Game/Rulesets/Mods/ModRelax.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Relax";
public override string ShortenedName => "RX";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax;
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) };
}
}
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 13fa61f536..15c24e2975 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -51,21 +52,15 @@ namespace osu.Game.Rulesets.Objects
private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
- private HitWindows hitWindows;
-
///
/// The hit windows for this .
///
- public HitWindows HitWindows
- {
- get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty));
- protected set => hitWindows = value;
- }
+ public HitWindows HitWindows { get; set; }
- private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+ private readonly Lazy> nestedHitObjects = new Lazy>(() => new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)));
[JsonIgnore]
- public IReadOnlyList NestedHitObjects => nestedHitObjects;
+ public IReadOnlyList NestedHitObjects => nestedHitObjects.Value;
///
/// Applies default values to this HitObject.
@@ -76,9 +71,19 @@ namespace osu.Game.Rulesets.Objects
{
ApplyDefaultsToSelf(controlPointInfo, difficulty);
- nestedHitObjects.Clear();
+ if (nestedHitObjects.IsValueCreated)
+ nestedHitObjects.Value.Clear();
+
CreateNestedHitObjects();
- nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty));
+
+ if (nestedHitObjects.IsValueCreated)
+ {
+ nestedHitObjects.Value.ForEach(h =>
+ {
+ h.HitWindows = HitWindows;
+ h.ApplyDefaults(controlPointInfo, difficulty);
+ });
+ }
}
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@@ -89,14 +94,24 @@ namespace osu.Game.Rulesets.Objects
Kiai = effectPoint.KiaiMode;
SampleControlPoint = samplePoint;
- overallDifficulty = difficulty.OverallDifficulty;
- hitWindows = null;
+ if (HitWindows == null)
+ HitWindows = CreateHitWindows();
+ HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
}
protected virtual void CreateNestedHitObjects()
{
}
- protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
+ protected void AddNested(HitObject hitObject) => nestedHitObjects.Value.Add(hitObject);
+
+ ///
+ /// Creates the for this .
+ /// This can be null to indicate that the has no .
+ ///
+ /// This will only be invoked if hasn't been set externally (e.g. from a .
+ ///
+ ///
+ protected virtual HitWindows CreateHitWindows() => new HitWindows();
}
}
diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs
index bf0878a408..3717209860 100644
--- a/osu.Game/Rulesets/Objects/HitWindows.cs
+++ b/osu.Game/Rulesets/Objects/HitWindows.cs
@@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Objects
public bool AllowsOk;
///
- /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window.
+ /// Sets hit windows with values that correspond to a difficulty parameter.
///
/// The parameter.
- public HitWindows(double difficulty)
+ public virtual void SetDifficulty(double difficulty)
{
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
@@ -135,39 +135,5 @@ namespace osu.Game.Rulesets.Objects
/// 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);
-
- ///
- /// Multiplies all hit windows by a value.
- ///
- /// The hit windows to multiply.
- /// The value to multiply each hit window by.
- public static HitWindows operator *(HitWindows windows, double value)
- {
- windows.Perfect *= value;
- windows.Great *= value;
- windows.Good *= value;
- windows.Ok *= value;
- windows.Meh *= value;
- windows.Miss *= value;
-
- return windows;
- }
-
- ///
- /// Divides all hit windows by a value.
- ///
- /// The hit windows to divide.
- /// The value to divide each hit window by.
- public static HitWindows operator /(HitWindows windows, double value)
- {
- windows.Perfect /= value;
- windows.Great /= value;
- windows.Good /= value;
- windows.Ok /= value;
- windows.Meh /= value;
- windows.Miss /= value;
-
- return windows;
- }
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
index 0db5a1dff1..ea4e7f6907 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index e3b35e2f8e..86a10fd363 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -12,5 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
index 32fb197c62..a8d7b23df1 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index c9b3046698..5a443c2ac2 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double Duration => EndTime - StartTime;
public float X { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
index f83173f498..f015272b2c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
index c6033d482c..ec5a002bbb 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index 28aac6862e..0141785f31 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -20,5 +20,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float X => Position.X;
public float Y => Position.Y;
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
index 72d18664bf..5e9786c84a 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
@@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
internal sealed class ConvertHit : HitObject, IHasCombo
{
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
index e810e687bd..8a9a0db0a7 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
@@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo
{
public bool NewCombo { get; set; }
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index 193e50aed6..4c8807a1d3 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs
index 86fe74f9af..3932d8ed9d 100644
--- a/osu.Game/Rulesets/Objects/SliderCurve.cs
+++ b/osu.Game/Rulesets/Objects/SliderCurve.cs
@@ -99,11 +99,9 @@ namespace osu.Game.Rulesets.Objects
cumulativeLength.Add(l);
}
- //TODO: Figure out if the following code is needed in some cases. Judging by the map
- // "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this.
// Lengthen slider curves that are too short compared to what's
// in the .osu file.
- /*if (l < Length && calculatedPath.Count > 1)
+ if (l < Distance && calculatedPath.Count > 1)
{
Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2];
double d = diff.Length;
@@ -111,9 +109,9 @@ namespace osu.Game.Rulesets.Objects
if (d <= 0)
return;
- calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d);
- cumulativeLength[calculatedPath.Count - 1] = Length;
- }*/
+ calculatedPath[calculatedPath.Count - 1] += diff * (float)((Distance - l) / d);
+ cumulativeLength[calculatedPath.Count - 1] = Distance;
+ }
}
public void Calculate()
diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
index adf35ce078..fdd528f296 100644
--- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
+++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Replays.Types
///
/// The to extract values from.
/// The beatmap.
- void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap);
+ void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap);
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index cd1d030afe..395eeab419 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets
{
@@ -22,8 +23,6 @@ namespace osu.Game.Rulesets
{
public readonly RulesetInfo RulesetInfo;
- public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { };
-
public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast()
// Confine all mods of each mod type into a single IEnumerable
.SelectMany(GetModsFor)
@@ -52,14 +51,17 @@ namespace osu.Game.Rulesets
/// Attempt to create a hit renderer for a beatmap
///
/// The beatmap to create the hit renderer for.
- /// Whether the hit renderer should assume the beatmap is for the current ruleset.
/// Unable to successfully load the beatmap to be usable with this ruleset.
///
- public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
+ public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap);
- public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
+ public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
- public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
+ public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
+
+ public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null);
+
+ public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null;
public virtual HitObjectComposer CreateHitObjectComposer() => null;
@@ -114,7 +116,8 @@ namespace osu.Game.Rulesets
Name = Description,
ShortName = ShortName,
InstantiationInfo = GetType().AssemblyQualifiedName,
- ID = LegacyID
+ ID = LegacyID,
+ Available = true
};
}
}
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 67a9a59d4a..1847b63658 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets
try
{
var assembly = Assembly.LoadFrom(file);
- loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset)));
+ loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
}
catch (Exception)
{
diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs
new file mode 100644
index 0000000000..bfb2b7c13b
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Rulesets.Scoring.Legacy
+{
+ ///
+ /// A which retrieves the applicable and
+ /// for the score from the database.
+ ///
+ public class DatabasedLegacyScoreParser : LegacyScoreParser
+ {
+ private readonly RulesetStore rulesets;
+ private readonly BeatmapManager beatmaps;
+
+ public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
+ {
+ this.rulesets = rulesets;
+ this.beatmaps = beatmaps;
+ }
+
+ protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
+ protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index 239f200e29..a90cd79186 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -14,18 +14,9 @@ using System.Linq;
namespace osu.Game.Rulesets.Scoring.Legacy
{
- public class LegacyScoreParser
+ public abstract class LegacyScoreParser
{
- private readonly RulesetStore rulesets;
- private readonly BeatmapManager beatmaps;
-
- public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
- {
- this.rulesets = rulesets;
- this.beatmaps = beatmaps;
- }
-
- private Beatmap currentBeatmap;
+ private IBeatmap currentBeatmap;
private Ruleset currentRuleset;
public Score Parse(Stream stream)
@@ -34,33 +25,35 @@ namespace osu.Game.Rulesets.Scoring.Legacy
using (SerializationReader sr = new SerializationReader(stream))
{
- score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) };
- currentRuleset = score.Ruleset.CreateInstance();
+ currentRuleset = GetRuleset(sr.ReadByte());
+ score = new Score { Ruleset = currentRuleset.RulesetInfo };
/* score.Pass = true;*/
var version = sr.ReadInt32();
/* score.FileChecksum = */
- var beatmapHash = sr.ReadString();
- score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
- currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
+ currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap;
+ score.Beatmap = currentBeatmap.BeatmapInfo;
/* score.PlayerName = */
score.User = new User { Username = sr.ReadString() };
/* var localScoreChecksum = */
sr.ReadString();
- /* score.Count300 = */
- sr.ReadUInt16();
- /* score.Count100 = */
- sr.ReadUInt16();
- /* score.Count50 = */
- sr.ReadUInt16();
- /* score.CountGeki = */
- sr.ReadUInt16();
- /* score.CountKatu = */
- sr.ReadUInt16();
- /* score.CountMiss = */
- sr.ReadUInt16();
+
+ var count300 = sr.ReadUInt16();
+ var count100 = sr.ReadUInt16();
+ var count50 = sr.ReadUInt16();
+ var countGeki = sr.ReadUInt16();
+ var countKatu = sr.ReadUInt16();
+ var countMiss = sr.ReadUInt16();
+
+ score.Statistics[HitResult.Great] = count300;
+ score.Statistics[HitResult.Good] = count100;
+ score.Statistics[HitResult.Meh] = count50;
+ score.Statistics[HitResult.Perfect] = countGeki;
+ score.Statistics[HitResult.Ok] = countKatu;
+ score.Statistics[HitResult.Miss] = countMiss;
+
score.TotalScore = sr.ReadInt32();
score.MaxCombo = sr.ReadUInt16();
/* score.Perfect = */
@@ -81,6 +74,34 @@ namespace osu.Game.Rulesets.Scoring.Legacy
/*OnlineId =*/
sr.ReadInt32();
+ switch (score.Ruleset.ID)
+ {
+ case 0:
+ {
+ int totalHits = count50 + count100 + count300 + countMiss;
+ score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1;
+ break;
+ }
+ case 1:
+ {
+ int totalHits = count50 + count100 + count300 + countMiss;
+ score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1;
+ break;
+ }
+ case 2:
+ {
+ int totalHits = count50 + count100 + count300 + countMiss + countKatu;
+ score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1;
+ break;
+ }
+ case 3:
+ {
+ int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu;
+ score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1;
+ break;
+ }
+ }
+
using (var replayInStream = new MemoryStream(compressedReplay))
{
byte[] properties = new byte[5];
@@ -150,5 +171,19 @@ namespace osu.Game.Rulesets.Scoring.Legacy
return frame;
}
+
+ ///
+ /// Retrieves the for a specific id.
+ ///
+ /// The id.
+ /// The .
+ protected abstract Ruleset GetRuleset(int rulesetId);
+
+ ///
+ /// Retrieves the corresponding to an MD5 hash.
+ ///
+ /// The MD5 hash.
+ /// The .
+ protected abstract WorkingBeatmap GetBeatmap(string md5Hash);
}
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs
index 957fd037e0..69d25fcb67 100644
--- a/osu.Game/Rulesets/Scoring/ScoreStore.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring
public Score ReadReplayFile(string replayFilename)
{
using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
- return new LegacyScoreParser(rulesets, beatmaps).Parse(s);
+ return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(s);
}
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index d1f1807937..e42e74c245 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -190,11 +190,6 @@ namespace osu.Game.Rulesets.UI
///
protected readonly WorkingBeatmap WorkingBeatmap;
- ///
- /// Whether the specified beatmap is assumed to be specific to the current ruleset.
- ///
- public readonly bool IsForCurrentRuleset;
-
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);
protected override Container Content => content;
@@ -206,43 +201,18 @@ namespace osu.Game.Rulesets.UI
///
/// The ruleset being repesented.
/// The beatmap to create the hit renderer for.
- /// Whether to assume the beatmap is for the current ruleset.
- protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset)
+ protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap)
: base(ruleset)
{
Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap.");
WorkingBeatmap = workingBeatmap;
- IsForCurrentRuleset = isForCurrentRuleset;
// ReSharper disable once PossibleNullReferenceException
Mods = workingBeatmap.Mods.Value;
RelativeSizeAxes = Axes.Both;
- BeatmapConverter converter = CreateBeatmapConverter();
- BeatmapProcessor processor = CreateBeatmapProcessor();
-
- // Check if the beatmap can be converted
- if (!converter.CanConvert(workingBeatmap.Beatmap))
- throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
-
- // Apply conversion adjustments before converting
- foreach (var mod in Mods.OfType>())
- mod.ApplyToBeatmapConverter(converter);
-
- // Convert the beatmap
- Beatmap = converter.Convert(workingBeatmap.Beatmap);
-
- // Apply difficulty adjustments from mods before using Difficulty.
- foreach (var mod in Mods.OfType())
- mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
-
- // Post-process the beatmap
- processor.PostProcess(Beatmap);
-
- // Apply defaults
- foreach (var h in Beatmap.HitObjects)
- h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
+ Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
@@ -277,10 +247,6 @@ namespace osu.Game.Rulesets.UI
if (mods == null)
return;
- foreach (var mod in mods.OfType>())
- foreach (var obj in Beatmap.HitObjects)
- mod.ApplyToHitObject(obj);
-
foreach (var mod in mods.OfType>())
mod.ApplyToRulesetContainer(this);
}
@@ -324,13 +290,6 @@ namespace osu.Game.Rulesets.UI
Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea;
}
- ///
- /// Creates a processor to perform post-processing operations
- /// on HitObjects in converted Beatmaps.
- ///
- /// The Beatmap processor.
- protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor();
-
///
/// Computes the size of the in relative coordinate space after aspect adjustments.
///
@@ -344,12 +303,6 @@ namespace osu.Game.Rulesets.UI
///
protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default
- ///
- /// Creates a converter to convert Beatmap to a specific mode.
- ///
- /// The Beatmap converter.
- protected abstract BeatmapConverter CreateBeatmapConverter();
-
///
/// Creates a DrawableHitObject from a HitObject.
///
@@ -377,9 +330,8 @@ namespace osu.Game.Rulesets.UI
///
/// The ruleset being repesented.
/// The beatmap to create the hit renderer for.
- /// Whether to assume the beatmap is for the current ruleset.
- protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
index 1e7f561aba..6f86d20295 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
@@ -4,9 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK.Input;
@@ -90,10 +88,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
switch (args.Key)
{
case Key.Minus:
- transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
break;
case Key.Plus:
- transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
break;
}
}
@@ -101,27 +99,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
return false;
}
- private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None)
- {
- this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing));
- }
-
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction);
-
- private class TransformVisibleTimeRange : Transform
- {
- private double valueAt(double time)
- {
- if (time < StartTime) return StartValue;
- if (time >= EndTime) return EndValue;
-
- return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
- }
-
- public override string TargetMember => "VisibleTimeRange.Value";
-
- protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time);
- protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value;
- }
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
index 79f9eaa9e9..3fc67e4e34 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
@@ -8,7 +8,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Timing;
@@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default);
- protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
- : base(ruleset, beatmap, isForCurrentRuleset)
+ protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
{
}
@@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (index < 0)
return new MultiplierControlPoint(time);
- return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone());
+ return new MultiplierControlPoint(time, DefaultControlPoints[index]);
}
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index adb749b492..be08fffc77 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -26,7 +26,8 @@ namespace osu.Game.Screens.Edit
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
+ public override bool AllowBeatmapRulesetChange => false;
private Box bottomBackground;
private Container screenContainer;
@@ -47,7 +48,7 @@ namespace osu.Game.Screens.Edit
{
// TODO: should probably be done at a RulesetContainer level to share logic with Player.
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
- clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false };
+ clock = new EditorClock(Beatmap, beatDivisor) { IsCoupled = false };
clock.ChangeSource(sourceClock);
dependencies.CacheAs(clock);
diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs
index 67025f0620..72fb91e7df 100644
--- a/osu.Game/Screens/Edit/EditorClock.cs
+++ b/osu.Game/Screens/Edit/EditorClock.cs
@@ -3,10 +3,13 @@
using System;
using System.Linq;
+using osu.Framework.Configuration;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Edit.Screens.Compose;
+using OpenTK;
namespace osu.Game.Screens.Edit
{
@@ -15,15 +18,26 @@ namespace osu.Game.Screens.Edit
///
public class EditorClock : DecoupleableInterpolatingFramedClock
{
+ public readonly double TrackLength;
+
public ControlPointInfo ControlPointInfo;
private readonly BindableBeatDivisor beatDivisor;
- public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor)
+ public EditorClock(Bindable beatmap, BindableBeatDivisor beatDivisor)
+ {
+ this.beatDivisor = beatDivisor;
+
+ ControlPointInfo = beatmap.Value.Beatmap.ControlPointInfo;
+ TrackLength = beatmap.Value.Track.Length;
+ }
+
+ public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor)
{
this.beatDivisor = beatDivisor;
ControlPointInfo = controlPointInfo;
+ TrackLength = trackLength;
}
///
@@ -111,6 +125,8 @@ namespace osu.Game.Screens.Edit
if (seekTime > nextTimingPoint?.Time)
seekTime = nextTimingPoint.Time;
+ // Ensure the sought point is within the boundaries
+ seekTime = MathHelper.Clamp(seekTime, 0, TrackLength);
Seek(seekTime);
}
}
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index 1d152361df..fb5c5ca84b 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -17,7 +17,9 @@ namespace osu.Game.Screens
{
private bool showDisclaimer;
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
+
+ protected override bool AllowBackButton => false;
public Loader()
{
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index f48e1925c5..8b88204ed0 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -6,26 +6,29 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework;
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.Graphics.Shapes;
using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Threading;
using osu.Game.Graphics;
+using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Audio;
-using osu.Framework.Configuration;
-using osu.Framework.Threading;
namespace osu.Game.Screens.Menu
{
- public class ButtonSystem : Container, IStateful
+ public class ButtonSystem : Container, IStateful, IKeyBindingHandler
{
public event Action StateChanged;
- private readonly BindableBool showOverlays = new BindableBool();
+ private readonly BindableBool hideOverlaysOnEnter = new BindableBool();
+ private readonly BindableBool allowOpeningOverlays = new BindableBool();
public Action OnEdit;
public Action OnExit;
@@ -133,7 +136,12 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuGame game)
{
- if (game != null) showOverlays.BindTo(game.ShowOverlays);
+ if (game != null)
+ {
+ hideOverlaysOnEnter.BindTo(game.HideOverlaysOnEnter);
+ allowOpeningOverlays.BindTo(game.AllowOpeningOverlays);
+ }
+
sampleBack = audio.Sample.Get(@"Menu/button-back-select");
}
@@ -147,22 +155,49 @@ namespace osu.Game.Screens.Menu
logo?.TriggerOnClick(state);
return true;
case Key.Escape:
- switch (State)
- {
- case MenuState.TopLevel:
- State = MenuState.Initial;
- return true;
- case MenuState.Play:
- backButton.TriggerOnClick();
- return true;
- }
-
- return false;
+ return goBack();
}
return false;
}
+ public bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Back:
+ return goBack();
+ default:
+ return false;
+ }
+ }
+
+ private bool goBack()
+ {
+ switch (State)
+ {
+ case MenuState.TopLevel:
+ State = MenuState.Initial;
+ return true;
+ case MenuState.Play:
+ backButton.TriggerOnClick();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public bool OnReleased(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Back:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private void onPlay()
{
State = MenuState.Play;
@@ -300,7 +335,8 @@ namespace osu.Game.Screens.Menu
logoDelayedAction = Scheduler.AddDelayed(() =>
{
- showOverlays.Value = false;
+ hideOverlaysOnEnter.Value = true;
+ allowOpeningOverlays.Value = false;
logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.Both;
@@ -329,7 +365,9 @@ namespace osu.Game.Screens.Menu
logoTracking = true;
logo.Impact();
- showOverlays.Value = true;
+
+ hideOverlaysOnEnter.Value = false;
+ allowOpeningOverlays.Value = true;
}, 200);
break;
default:
@@ -337,6 +375,7 @@ namespace osu.Game.Screens.Menu
logo.ScaleTo(0.5f, 200, Easing.OutQuint);
break;
}
+
break;
case MenuState.EnteringMode:
logoTracking = true;
diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs
index 5af634b02d..b8cb7f2a4a 100644
--- a/osu.Game/Screens/Menu/Disclaimer.cs
+++ b/osu.Game/Screens/Menu/Disclaimer.cs
@@ -18,7 +18,9 @@ namespace osu.Game.Screens.Menu
private readonly SpriteIcon icon;
private Color4 iconColour;
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
+ protected override bool AllowOpeningOverlays => false;
+
public override bool CursorVisible => false;
public Disclaimer()
diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
new file mode 100644
index 0000000000..62605da5a4
--- /dev/null
+++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Input;
+using osu.Game.Overlays;
+using OpenTK.Input;
+
+namespace osu.Game.Screens.Menu
+{
+ public class ExitConfirmOverlay : HoldToConfirmOverlay
+ {
+ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ {
+ if (args.Key == Key.Escape && !args.Repeat)
+ {
+ BeginConfirm();
+ return true;
+ }
+
+ return base.OnKeyDown(state, args);
+ }
+
+ protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
+ {
+ if (args.Key == Key.Escape)
+ {
+ AbortConfirm();
+ return true;
+ }
+
+ return base.OnKeyUp(state, args);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs
index 4de76e530a..c174e2d470 100644
--- a/osu.Game/Screens/Menu/Intro.cs
+++ b/osu.Game/Screens/Menu/Intro.cs
@@ -31,7 +31,9 @@ namespace osu.Game.Screens.Menu
private SampleChannel welcome;
private SampleChannel seeya;
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
+ protected override bool AllowOpeningOverlays => false;
+
public override bool CursorVisible => false;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty();
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index f2ea6d85a8..d5f3b11467 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
using osu.Game.Screens.Direct;
using osu.Game.Screens.Edit;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi;
using osu.Game.Screens.Select;
using osu.Game.Screens.Tournament;
@@ -24,7 +24,10 @@ namespace osu.Game.Screens.Menu
{
private readonly ButtonSystem buttons;
- public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial;
+ protected override bool HideOverlaysOnEnter => buttons.State == MenuState.Initial;
+ protected override bool AllowOpeningOverlays => buttons.State != MenuState.Initial;
+
+ protected override bool AllowBackButton => buttons.State != MenuState.Initial;
private readonly BackgroundScreenDefault background;
private Screen songSelect;
@@ -39,6 +42,10 @@ namespace osu.Game.Screens.Menu
Children = new Drawable[]
{
+ new ExitConfirmOverlay
+ {
+ Action = Exit,
+ },
new ParallaxContainer
{
ParallaxAmount = 0.01f,
@@ -50,7 +57,7 @@ namespace osu.Game.Screens.Menu
OnDirect = delegate { Push(new OnlineListing()); },
OnEdit = delegate { Push(new Editor()); },
OnSolo = delegate { Push(consumeSongSelect()); },
- OnMulti = delegate { Push(new Lobby()); },
+ OnMulti = delegate { Push(new Multiplayer()); },
OnExit = Exit,
}
}
diff --git a/osu.Game/Screens/Multiplayer/DrawableGameType.cs b/osu.Game/Screens/Multi/Components/DrawableGameType.cs
similarity index 96%
rename from osu.Game/Screens/Multiplayer/DrawableGameType.cs
rename to osu.Game/Screens/Multi/Components/DrawableGameType.cs
index 5790008f76..3406e179d4 100644
--- a/osu.Game/Screens/Multiplayer/DrawableGameType.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableGameType.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Components
{
public class DrawableGameType : CircularContainer, IHasTooltip
{
diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
new file mode 100644
index 0000000000..1851f4618e
--- /dev/null
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
@@ -0,0 +1,317 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Users;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Multi.Components
+{
+ public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable
+ {
+ public const float SELECTION_BORDER_WIDTH = 4;
+ private const float corner_radius = 5;
+ private const float transition_duration = 60;
+ private const float content_padding = 10;
+ private const float height = 100;
+ private const float side_strip_width = 5;
+ private const float cover_width = 145;
+
+ private readonly Box selectionBox;
+
+ private readonly Bindable nameBind = new Bindable();
+ private readonly Bindable hostBind = new Bindable();
+ private readonly Bindable statusBind = new Bindable();
+ private readonly Bindable typeBind = new Bindable();
+ private readonly Bindable beatmapBind = new Bindable();
+ private readonly Bindable participantsBind = new Bindable();
+
+ public readonly Room Room;
+
+ private SelectionState state;
+ public SelectionState State
+ {
+ get { return state; }
+ set
+ {
+ if (value == state) return;
+ state = value;
+
+ if (state == SelectionState.Selected)
+ selectionBox.FadeIn(transition_duration);
+ else
+ selectionBox.FadeOut(transition_duration);
+
+ StateChanged?.Invoke(State);
+ }
+ }
+
+ public IEnumerable FilterTerms => new[] { Room.Name.Value };
+
+ private bool matchingFilter;
+ public bool MatchingFilter
+ {
+ get { return matchingFilter; }
+ set
+ {
+ matchingFilter = value;
+ this.FadeTo(MatchingFilter ? 1 : 0, 200);
+ }
+ }
+
+ private Action action;
+ public new Action Action
+ {
+ get { return action; }
+ set
+ {
+ action = value;
+ Enabled.Value = action != null;
+ }
+ }
+
+ public event Action StateChanged;
+
+ public DrawableRoom(Room room)
+ {
+ Room = room;
+
+ RelativeSizeAxes = Axes.X;
+ Height = height + SELECTION_BORDER_WIDTH * 2;
+ CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
+ Masking = true;
+
+ // create selectionBox here so State can be set before being loaded
+ selectionBox = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0f,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, LocalisationEngine localisation)
+ {
+ Box sideStrip;
+ Container coverContainer;
+ OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
+ ParticipantInfo participantInfo;
+ ModeTypeInfo modeTypeInfo;
+
+ Children = new Drawable[]
+ {
+ selectionBox,
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(SELECTION_BORDER_WIDTH),
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = corner_radius,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(40),
+ Radius = 5,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex(@"212121"),
+ },
+ sideStrip = new Box
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = side_strip_width,
+ },
+ new Container
+ {
+ Width = cover_width,
+ RelativeSizeAxes = Axes.Y,
+ Masking = true,
+ Margin = new MarginPadding { Left = side_strip_width },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ coverContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding
+ {
+ Vertical = content_padding,
+ Left = side_strip_width + cover_width + content_padding,
+ Right = content_padding,
+ },
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(5f),
+ Children = new Drawable[]
+ {
+ name = new OsuSpriteText
+ {
+ TextSize = 18,
+ },
+ participantInfo = new ParticipantInfo(),
+ },
+ },
+ new FillFlowContainer
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ status = new OsuSpriteText
+ {
+ TextSize = 14,
+ Font = @"Exo2.0-Bold",
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Colour = colours.Gray9,
+ Direction = FillDirection.Horizontal,
+ Children = new[]
+ {
+ beatmapTitle = new OsuSpriteText
+ {
+ TextSize = 14,
+ Font = @"Exo2.0-BoldItalic",
+ },
+ beatmapDash = new OsuSpriteText
+ {
+ TextSize = 14,
+ Font = @"Exo2.0-BoldItalic",
+ },
+ beatmapArtist = new OsuSpriteText
+ {
+ TextSize = 14,
+ Font = @"Exo2.0-RegularItalic",
+ },
+ },
+ },
+ },
+ },
+ modeTypeInfo = new ModeTypeInfo
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ nameBind.ValueChanged += n => name.Text = n;
+ hostBind.ValueChanged += h => participantInfo.Host = h;
+ typeBind.ValueChanged += m => modeTypeInfo.Type = m;
+ participantsBind.ValueChanged += p => participantInfo.Participants = p;
+
+ statusBind.ValueChanged += s =>
+ {
+ status.Text = s.Message;
+
+ foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status })
+ d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
+ };
+
+ beatmapBind.ValueChanged += b =>
+ {
+ modeTypeInfo.Beatmap = b;
+
+ if (b != null)
+ {
+ coverContainer.FadeIn(transition_duration);
+
+ LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fill,
+ OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
+ }, coverContainer.Add);
+
+ beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
+ beatmapDash.Text = @" - ";
+ beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
+ }
+ else
+ {
+ coverContainer.FadeOut(transition_duration);
+
+ beatmapTitle.Current = null;
+ beatmapArtist.Current = null;
+
+ beatmapTitle.Text = "Changing map";
+ beatmapDash.Text = beatmapArtist.Text = string.Empty;
+ }
+ };
+
+ nameBind.BindTo(Room.Name);
+ hostBind.BindTo(Room.Host);
+ statusBind.BindTo(Room.Status);
+ typeBind.BindTo(Room.Type);
+ beatmapBind.BindTo(Room.Beatmap);
+ participantsBind.BindTo(Room.Participants);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ this.FadeInFromZero(transition_duration);
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if (Enabled.Value)
+ {
+ Action?.Invoke(this);
+ State = SelectionState.Selected;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multiplayer/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
similarity index 98%
rename from osu.Game/Screens/Multiplayer/ModeTypeInfo.cs
rename to osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
index 08e96ba55d..e3aba685a7 100644
--- a/osu.Game/Screens/Multiplayer/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
@@ -1,14 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.Multiplayer;
+using OpenTK;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Components
{
public class ModeTypeInfo : Container
{
diff --git a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs
similarity index 99%
rename from osu.Game/Screens/Multiplayer/ParticipantInfo.cs
rename to osu.Game/Screens/Multi/Components/ParticipantInfo.cs
index ff1887fa17..ab404488f1 100644
--- a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -11,8 +10,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
+using OpenTK;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Components
{
public class ParticipantInfo : Container
{
diff --git a/osu.Game/Screens/Multiplayer/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
similarity index 72%
rename from osu.Game/Screens/Multiplayer/RoomInspector.cs
rename to osu.Game/Screens/Multi/Components/RoomInspector.cs
index bfc4a44ed5..3bd054b042 100644
--- a/osu.Game/Screens/Multiplayer/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
-using OpenTK;
-using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
@@ -20,22 +18,16 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
+using OpenTK;
+using OpenTK.Graphics;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Components
{
public class RoomInspector : Container
{
- private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
private const float transition_duration = 100;
- private readonly Box statusStrip;
- private readonly Container coverContainer;
- private readonly FillFlowContainer topFlow, participantsFlow;
- private readonly ModeTypeInfo modeTypeInfo;
- private readonly OsuSpriteText participants, participantsSlash, maxParticipants, name, status, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
- private readonly ParticipantInfo participantInfo;
- private readonly ScrollContainer participantsScroll;
-
+ private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
private readonly Bindable nameBind = new Bindable();
private readonly Bindable hostBind = new Bindable();
private readonly Bindable statusBind = new Bindable();
@@ -45,10 +37,14 @@ namespace osu.Game.Screens.Multiplayer
private readonly Bindable participantsBind = new Bindable();
private OsuColour colours;
- private LocalisationEngine localisation;
+ private Box statusStrip;
+ private Container coverContainer;
+ private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow;
+ private OsuSpriteText name, status;
+ private ScrollContainer participantsScroll;
+ private ParticipantInfo participantInfo;
private Room room;
-
public Room Room
{
get { return room; }
@@ -57,20 +53,36 @@ namespace osu.Game.Screens.Multiplayer
if (value == room) return;
room = value;
- nameBind.BindTo(Room.Name);
- hostBind.BindTo(Room.Host);
- statusBind.BindTo(Room.Status);
- typeBind.BindTo(Room.Type);
- beatmapBind.BindTo(Room.Beatmap);
- maxParticipantsBind.BindTo(Room.MaxParticipants);
- participantsBind.BindTo(Room.Participants);
+ nameBind.UnbindBindings();
+ hostBind.UnbindBindings();
+ statusBind.UnbindBindings();
+ typeBind.UnbindBindings();
+ beatmapBind.UnbindBindings();
+ maxParticipantsBind.UnbindBindings();
+ participantsBind.UnbindBindings();
+
+ if (room != null)
+ {
+ nameBind.BindTo(room.Name);
+ hostBind.BindTo(room.Host);
+ statusBind.BindTo(room.Status);
+ typeBind.BindTo(room.Type);
+ beatmapBind.BindTo(room.Beatmap);
+ maxParticipantsBind.BindTo(room.MaxParticipants);
+ participantsBind.BindTo(room.Participants);
+ }
+
+ updateState();
}
}
- public RoomInspector()
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, LocalisationEngine localisation)
{
- Width = 520;
- RelativeSizeAxes = Axes.Y;
+ this.colours = colours;
+
+ ModeTypeInfo modeTypeInfo;
+ OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
Children = new Drawable[]
{
@@ -120,7 +132,7 @@ namespace osu.Game.Screens.Multiplayer
Padding = new MarginPadding(20),
Children = new Drawable[]
{
- new FillFlowContainer
+ participantNumbersFlow = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@@ -178,6 +190,7 @@ namespace osu.Game.Screens.Multiplayer
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
+ LayoutDuration = transition_duration,
Padding = contentPadding,
Spacing = new Vector2(0f, 5f),
Children = new Drawable[]
@@ -187,7 +200,7 @@ namespace osu.Game.Screens.Multiplayer
TextSize = 14,
Font = @"Exo2.0-Bold",
},
- new FillFlowContainer
+ infoPanelFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 30,
@@ -229,6 +242,7 @@ namespace osu.Game.Screens.Multiplayer
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 14,
+ Colour = colours.Gray9,
},
},
},
@@ -269,27 +283,68 @@ namespace osu.Game.Screens.Multiplayer
},
};
- nameBind.ValueChanged += displayName;
- hostBind.ValueChanged += displayUser;
- typeBind.ValueChanged += displayGameType;
- maxParticipantsBind.ValueChanged += displayMaxParticipants;
- participantsBind.ValueChanged += displayParticipants;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours, LocalisationEngine localisation)
- {
- this.localisation = localisation;
- this.colours = colours;
-
- beatmapAuthor.Colour = colours.Gray9;
-
- //binded here instead of ctor because dependencies are needed
+ nameBind.ValueChanged += n => name.Text = n;
+ hostBind.ValueChanged += h => participantInfo.Host = h;
+ typeBind.ValueChanged += t => modeTypeInfo.Type = t;
statusBind.ValueChanged += displayStatus;
- beatmapBind.ValueChanged += displayBeatmap;
- statusBind.TriggerChange();
- beatmapBind.TriggerChange();
+ beatmapBind.ValueChanged += b =>
+ {
+ modeTypeInfo.Beatmap = b;
+
+ if (b != null)
+ {
+ coverContainer.FadeIn(transition_duration);
+
+ LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fill,
+ OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
+ }, coverContainer.Add);
+
+ beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
+ beatmapDash.Text = @" - ";
+ beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
+ beatmapAuthor.Text = $"mapped by {b.Metadata.Author}";
+ }
+ else
+ {
+ coverContainer.FadeOut(transition_duration);
+
+ beatmapTitle.Current = null;
+ beatmapArtist.Current = null;
+
+ beatmapTitle.Text = "Changing map";
+ beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
+ }
+ };
+
+ maxParticipantsBind.ValueChanged += m =>
+ {
+ if (m == null)
+ {
+ participantsSlash.FadeOut(transition_duration);
+ maxParticipants.FadeOut(transition_duration);
+ }
+ else
+ {
+ participantsSlash.FadeIn(transition_duration);
+ maxParticipants.FadeIn(transition_duration);
+ maxParticipants.Text = m.ToString();
+ }
+ };
+
+ participantsBind.ValueChanged += p =>
+ {
+ participants.Text = p.Length.ToString();
+ participantInfo.Participants = p;
+ participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
+ };
+
+ updateState();
}
protected override void UpdateAfterChildren()
@@ -299,84 +354,39 @@ namespace osu.Game.Screens.Multiplayer
participantsScroll.Height = DrawHeight - topFlow.DrawHeight;
}
- private void displayName(string value)
+ private void displayStatus(RoomStatus s)
{
- name.Text = value;
+ status.Text = s.Message;
+
+ Color4 c = s.GetAppropriateColour(colours);
+ statusStrip.FadeColour(c, transition_duration);
+ status.FadeColour(c, transition_duration);
}
- private void displayUser(User value)
+ private void updateState()
{
- participantInfo.Host = value;
- }
-
- private void displayStatus(RoomStatus value)
- {
- status.Text = value.Message;
-
- foreach (Drawable d in new Drawable[] { statusStrip, status })
- d.FadeColour(value.GetAppropriateColour(colours), transition_duration);
- }
-
- private void displayGameType(GameType value)
- {
- modeTypeInfo.Type = value;
- }
-
- private void displayBeatmap(BeatmapInfo value)
- {
- modeTypeInfo.Beatmap = value;
-
- if (value != null)
- {
- coverContainer.FadeIn(transition_duration);
-
- LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- FillMode = FillMode.Fill,
- OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
- },
- coverContainer.Add);
-
- beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
- beatmapDash.Text = @" - ";
- beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
- beatmapAuthor.Text = $"mapped by {value.Metadata.Author}";
- }
- else
+ if (Room == null)
{
coverContainer.FadeOut(transition_duration);
+ participantsFlow.FadeOut(transition_duration);
+ participantNumbersFlow.FadeOut(transition_duration);
+ infoPanelFlow.FadeOut(transition_duration);
+ name.FadeOut(transition_duration);
+ participantInfo.FadeOut(transition_duration);
- beatmapTitle.Current = null;
- beatmapArtist.Current = null;
-
- beatmapTitle.Text = "Changing map";
- beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
- }
- }
-
- private void displayMaxParticipants(int? value)
- {
- if (value == null)
- {
- participantsSlash.FadeOut(transition_duration);
- maxParticipants.FadeOut(transition_duration);
+ displayStatus(new RoomStatusNoneSelected());
}
else
{
- participantsSlash.FadeIn(transition_duration);
- maxParticipants.FadeIn(transition_duration);
- maxParticipants.Text = value.ToString();
- }
- }
+ participantsFlow.FadeIn(transition_duration);
+ participantNumbersFlow.FadeIn(transition_duration);
+ infoPanelFlow.FadeIn(transition_duration);
+ name.FadeIn(transition_duration);
+ participantInfo.FadeIn(transition_duration);
- private void displayParticipants(User[] value)
- {
- participants.Text = value.Length.ToString();
- participantInfo.Participants = value;
- participantsFlow.ChildrenEnumerable = value.Select(u => new UserTile(u));
+ statusBind.TriggerChange();
+ beatmapBind.TriggerChange();
+ }
}
private class UserTile : Container, IHasTooltip
@@ -407,5 +417,11 @@ namespace osu.Game.Screens.Multiplayer
};
}
}
+
+ private class RoomStatusNoneSelected : RoomStatus
+ {
+ public override string Message => @"No Room Selected";
+ public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8;
+ }
}
}
diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs
new file mode 100644
index 0000000000..46610a36d8
--- /dev/null
+++ b/osu.Game/Screens/Multi/Header.cs
@@ -0,0 +1,113 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.SearchableList;
+using osu.Game.Screens.Multi.Screens;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Multi
+{
+ public class Header : Container
+ {
+ public const float HEIGHT = 121;
+
+ private readonly OsuSpriteText screenType;
+ private readonly HeaderBreadcrumbControl breadcrumbs;
+
+ public Header(Screen initialScreen)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = HEIGHT;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex(@"2f2043"),
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.BottomLeft,
+ Position = new Vector2(-35f, 5f),
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10f, 0f),
+ Children = new Drawable[]
+ {
+ new SpriteIcon
+ {
+ Size = new Vector2(25),
+ Icon = FontAwesome.fa_osu_multi,
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new[]
+ {
+ new OsuSpriteText
+ {
+ Text = "multiplayer ",
+ TextSize = 25,
+ },
+ screenType = new OsuSpriteText
+ {
+ TextSize = 25,
+ Font = @"Exo2.0-Light",
+ },
+ },
+ },
+ },
+ },
+ breadcrumbs = new HeaderBreadcrumbControl(initialScreen)
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ },
+ },
+ },
+ };
+
+ breadcrumbs.Current.ValueChanged += s => screenType.Text = ((MultiplayerScreen)s).Type.ToLower();
+ breadcrumbs.Current.TriggerChange();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ screenType.Colour = colours.Yellow;
+ breadcrumbs.StripColour = colours.Green;
+ }
+
+ private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
+ {
+ public HeaderBreadcrumbControl(Screen initialScreen) : base(initialScreen)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ AccentColour = Color4.White;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
new file mode 100644
index 0000000000..7822fa68dc
--- /dev/null
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -0,0 +1,108 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
+using osu.Game.Screens.Menu;
+using osu.Game.Screens.Multi.Screens.Lounge;
+
+namespace osu.Game.Screens.Multi
+{
+ public class Multiplayer : OsuScreen
+ {
+ private readonly MultiplayerWaveContainer waves;
+
+ protected override Container Content => waves;
+
+ public Multiplayer()
+ {
+ InternalChild = waves = new MultiplayerWaveContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ Lounge lounge;
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex(@"3e3a44"),
+ },
+ new Triangles
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColourLight = OsuColour.FromHex(@"3c3842"),
+ ColourDark = OsuColour.FromHex(@"393540"),
+ TriangleScale = 5,
+ },
+ },
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = Header.HEIGHT },
+ Child = lounge = new Lounge(),
+ },
+ new Header(lounge),
+ };
+
+ lounge.Exited += s => Exit();
+ }
+
+ protected override void OnEntering(Screen last)
+ {
+ base.OnEntering(last);
+ waves.Show();
+ }
+
+ protected override bool OnExiting(Screen next)
+ {
+ waves.Hide();
+ return base.OnExiting(next);
+ }
+
+ protected override void OnResuming(Screen last)
+ {
+ base.OnResuming(last);
+ waves.Show();
+ }
+
+ protected override void OnSuspending(Screen next)
+ {
+ base.OnSuspending(next);
+ waves.Hide();
+ }
+
+ protected override void LogoExiting(OsuLogo logo)
+ {
+ // the wave overlay transition takes longer than expected to run.
+ logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut();
+ base.LogoExiting(logo);
+ }
+
+ private class MultiplayerWaveContainer : WaveContainer
+ {
+ protected override bool StartHidden => true;
+
+ public MultiplayerWaveContainer()
+ {
+ FirstWaveColour = OsuColour.FromHex(@"654d8c");
+ SecondWaveColour = OsuColour.FromHex(@"554075");
+ ThirdWaveColour = OsuColour.FromHex(@"44325e");
+ FourthWaveColour = OsuColour.FromHex(@"392850");
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs
new file mode 100644
index 0000000000..421c89479a
--- /dev/null
+++ b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Graphics;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Overlays.SearchableList;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Multi.Screens.Lounge
+{
+ public class FilterControl : SearchableListFilterControl
+ {
+ protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42");
+ protected override LoungeTab DefaultTab => LoungeTab.Public;
+
+ public FilterControl()
+ {
+ DisplayStyleControl.Hide();
+ }
+ }
+
+ public enum LoungeTab
+ {
+ Public = RoomAvailability.Public,
+ Private = RoomAvailability.FriendsOnly,
+ }
+}
diff --git a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs
new file mode 100644
index 0000000000..016babcaa5
--- /dev/null
+++ b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs
@@ -0,0 +1,191 @@
+// 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 osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+using osu.Framework.Screens;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Overlays.SearchableList;
+using osu.Game.Screens.Multi.Components;
+using OpenTK;
+
+namespace osu.Game.Screens.Multi.Screens.Lounge
+{
+ public class Lounge : MultiplayerScreen
+ {
+ private readonly Container content;
+ private readonly SearchContainer search;
+
+ protected readonly FilterControl Filter;
+ protected readonly FillFlowContainer RoomsContainer;
+ protected readonly RoomInspector Inspector;
+
+ public override string Title => "Lounge";
+
+ protected override Container TransitionContent => content;
+
+ private IEnumerable rooms;
+ public IEnumerable Rooms
+ {
+ get { return rooms; }
+ set
+ {
+ if (Equals(value, rooms)) return;
+ rooms = value;
+
+ var enumerable = rooms.ToList();
+
+ RoomsContainer.Children = enumerable.Select(r => new DrawableRoom(r)
+ {
+ Action = didSelect,
+ }).ToList();
+
+ if (!enumerable.Contains(Inspector.Room))
+ Inspector.Room = null;
+
+ filterRooms();
+ }
+ }
+
+ public Lounge()
+ {
+ Children = new Drawable[]
+ {
+ Filter = new FilterControl(),
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.55f,
+ Padding = new MarginPadding
+ {
+ Vertical = 35 - DrawableRoom.SELECTION_BORDER_WIDTH,
+ Right = 20 - DrawableRoom.SELECTION_BORDER_WIDTH
+ },
+ Child = search = new SearchContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = RoomsContainer = new RoomsFilterContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(10 - DrawableRoom.SELECTION_BORDER_WIDTH * 2),
+ },
+ },
+ },
+ Inspector = new RoomInspector
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.45f,
+ },
+ },
+ },
+ };
+
+ Filter.Search.Current.ValueChanged += s => filterRooms();
+ Filter.Tabs.Current.ValueChanged += t => filterRooms();
+ Filter.Search.Exit += Exit;
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ content.Padding = new MarginPadding
+ {
+ Top = Filter.DrawHeight,
+ Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH,
+ Right = SearchableListOverlay.WIDTH_PADDING,
+ };
+ }
+
+ protected override void OnFocus(InputState state)
+ {
+ GetContainingInputManager().ChangeFocus(Filter.Search);
+ }
+
+ protected override void OnEntering(Screen last)
+ {
+ base.OnEntering(last);
+ Filter.Search.HoldFocus = true;
+ }
+
+ protected override bool OnExiting(Screen next)
+ {
+ Filter.Search.HoldFocus = false;
+ return base.OnExiting(next);
+ }
+
+ protected override void OnResuming(Screen last)
+ {
+ base.OnResuming(last);
+ Filter.Search.HoldFocus = true;
+ }
+
+ protected override void OnSuspending(Screen next)
+ {
+ base.OnSuspending(next);
+ Filter.Search.HoldFocus = false;
+ }
+
+ private void filterRooms()
+ {
+ search.SearchTerm = Filter.Search.Current.Value ?? string.Empty;
+
+ foreach (DrawableRoom r in RoomsContainer.Children)
+ {
+ r.MatchingFilter = r.MatchingFilter &&
+ r.Room.Availability.Value == (RoomAvailability)Filter.Tabs.Current.Value;
+ }
+ }
+
+ private void didSelect(DrawableRoom room)
+ {
+ RoomsContainer.Children.ForEach(c =>
+ {
+ if (c != room)
+ c.State = SelectionState.NotSelected;
+ });
+
+ Inspector.Room = room.Room;
+
+ // open the room if its selected and is clicked again
+ if (room.State == SelectionState.Selected)
+ Push(new Match());
+ }
+
+ private class RoomsFilterContainer : FillFlowContainer, IHasFilterableChildren
+ {
+ public IEnumerable FilterTerms => new string[] { };
+ public IEnumerable FilterableChildren => Children;
+
+ public bool MatchingFilter
+ {
+ set
+ {
+ if (value)
+ InvalidateLayout();
+ }
+ }
+
+ public RoomsFilterContainer()
+ {
+ LayoutDuration = 200;
+ LayoutEasing = Easing.OutQuint;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multiplayer/Match.cs b/osu.Game/Screens/Multi/Screens/Match.cs
similarity index 96%
rename from osu.Game/Screens/Multiplayer/Match.cs
rename to osu.Game/Screens/Multi/Screens/Match.cs
index 5402e70ea5..4ba7fe9f6a 100644
--- a/osu.Game/Screens/Multiplayer/Match.cs
+++ b/osu.Game/Screens/Multi/Screens/Match.cs
@@ -3,14 +3,14 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
-using OpenTK.Graphics;
using osu.Game.Screens.Select;
-using osu.Framework.Graphics;
+using OpenTK.Graphics;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Screens
{
public class Match : ScreenWhiteBox
{
diff --git a/osu.Game/Screens/Multiplayer/MatchCreate.cs b/osu.Game/Screens/Multi/Screens/MatchCreate.cs
similarity index 91%
rename from osu.Game/Screens/Multiplayer/MatchCreate.cs
rename to osu.Game/Screens/Multi/Screens/MatchCreate.cs
index ca6b814cb9..6b4e26d5e5 100644
--- a/osu.Game/Screens/Multiplayer/MatchCreate.cs
+++ b/osu.Game/Screens/Multi/Screens/MatchCreate.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Screens
{
public class MatchCreate : ScreenWhiteBox
{
diff --git a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs
new file mode 100644
index 0000000000..fa9b40684c
--- /dev/null
+++ b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs
@@ -0,0 +1,57 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Screens;
+using osu.Game.Graphics.Containers;
+
+namespace osu.Game.Screens.Multi.Screens
+{
+ public abstract class MultiplayerScreen : OsuScreen
+ {
+ private const Easing in_easing = Easing.OutQuint;
+ private const Easing out_easing = Easing.InSine;
+
+ protected virtual Container TransitionContent => Content;
+
+ ///
+ /// The type to display in the title of the .
+ ///
+ public virtual string Type => Title;
+
+ protected override void OnEntering(Screen last)
+ {
+ base.OnEntering(last);
+
+ TransitionContent.MoveToX(200);
+
+ TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, in_easing);
+ TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
+ }
+
+ protected override bool OnExiting(Screen next)
+ {
+ Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing);
+ TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, out_easing);
+
+ return base.OnExiting(next);
+ }
+
+ protected override void OnResuming(Screen last)
+ {
+ base.OnResuming(last);
+
+ Content.FadeIn(WaveContainer.APPEAR_DURATION, in_easing);
+ TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
+ }
+
+ protected override void OnSuspending(Screen next)
+ {
+ base.OnSuspending(next);
+
+ Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing);
+ TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, out_easing);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
deleted file mode 100644
index d53100526f..0000000000
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using OpenTK;
-using OpenTK.Graphics;
-using osu.Framework.Allocation;
-using osu.Framework.Configuration;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Localisation;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Drawables;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Online.Multiplayer;
-using osu.Game.Users;
-
-namespace osu.Game.Screens.Multiplayer
-{
- public class DrawableRoom : OsuClickableContainer
- {
- private const float transition_duration = 100;
- private const float content_padding = 10;
- private const float height = 100;
- private const float side_strip_width = 5;
- private const float cover_width = 145;
-
- private readonly Box sideStrip;
- private readonly Container coverContainer;
- private readonly OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
- private readonly FillFlowContainer beatmapInfoFlow;
- private readonly ParticipantInfo participantInfo;
- private readonly ModeTypeInfo modeTypeInfo;
-
- private readonly Bindable nameBind = new Bindable();
- private readonly Bindable hostBind = new Bindable();
- private readonly Bindable statusBind = new Bindable();
- private readonly Bindable typeBind = new Bindable();
- private readonly Bindable beatmapBind = new Bindable();
- private readonly Bindable participantsBind = new Bindable();
-
- private OsuColour colours;
- private LocalisationEngine localisation;
-
- public readonly Room Room;
-
- public DrawableRoom(Room room)
- {
- Room = room;
-
- RelativeSizeAxes = Axes.X;
- Height = height;
- CornerRadius = 5;
- Masking = true;
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(40),
- Radius = 5,
- };
-
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex(@"212121"),
- },
- sideStrip = new Box
- {
- RelativeSizeAxes = Axes.Y,
- Width = side_strip_width,
- },
- new Container
- {
- Width = cover_width,
- RelativeSizeAxes = Axes.Y,
- Masking = true,
- Margin = new MarginPadding { Left = side_strip_width },
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- coverContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- },
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding
- {
- Vertical = content_padding,
- Left = side_strip_width + cover_width + content_padding,
- Right = content_padding,
- },
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(5f),
- Children = new Drawable[]
- {
- name = new OsuSpriteText
- {
- TextSize = 18,
- },
- participantInfo = new ParticipantInfo(),
- },
- },
- new FillFlowContainer
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- status = new OsuSpriteText
- {
- TextSize = 14,
- Font = @"Exo2.0-Bold",
- },
- beatmapInfoFlow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Children = new[]
- {
- beatmapTitle = new OsuSpriteText
- {
- TextSize = 14,
- Font = @"Exo2.0-BoldItalic",
- },
- beatmapDash = new OsuSpriteText
- {
- TextSize = 14,
- Font = @"Exo2.0-BoldItalic",
- },
- beatmapArtist = new OsuSpriteText
- {
- TextSize = 14,
- Font = @"Exo2.0-RegularItalic",
- },
- },
- },
- },
- },
- modeTypeInfo = new ModeTypeInfo
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- },
- },
- },
- };
-
- nameBind.ValueChanged += displayName;
- hostBind.ValueChanged += displayUser;
- typeBind.ValueChanged += displayGameType;
- participantsBind.ValueChanged += displayParticipants;
-
- nameBind.BindTo(Room.Name);
- hostBind.BindTo(Room.Host);
- statusBind.BindTo(Room.Status);
- typeBind.BindTo(Room.Type);
- beatmapBind.BindTo(Room.Beatmap);
- participantsBind.BindTo(Room.Participants);
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours, LocalisationEngine localisation)
- {
- this.localisation = localisation;
- this.colours = colours;
-
- beatmapInfoFlow.Colour = colours.Gray9;
-
- //binded here instead of ctor because dependencies are needed
- statusBind.ValueChanged += displayStatus;
- beatmapBind.ValueChanged += displayBeatmap;
-
- statusBind.TriggerChange();
- beatmapBind.TriggerChange();
- }
-
- private void displayName(string value)
- {
- name.Text = value;
- }
-
- private void displayUser(User value)
- {
- participantInfo.Host = value;
- }
-
- private void displayStatus(RoomStatus value)
- {
- if (value == null) return;
- status.Text = value.Message;
-
- foreach (Drawable d in new Drawable[] { sideStrip, status })
- d.FadeColour(value.GetAppropriateColour(colours), 100);
- }
-
- private void displayGameType(GameType value)
- {
- modeTypeInfo.Type = value;
- }
-
- private void displayBeatmap(BeatmapInfo value)
- {
- modeTypeInfo.Beatmap = value;
-
- if (value != null)
- {
- coverContainer.FadeIn(transition_duration);
-
- LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- FillMode = FillMode.Fill,
- OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
- },
- coverContainer.Add);
-
- beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
- beatmapDash.Text = @" - ";
- beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
- }
- else
- {
- coverContainer.FadeOut(transition_duration);
-
- beatmapTitle.Current = null;
- beatmapArtist.Current = null;
-
- beatmapTitle.Text = "Changing map";
- beatmapDash.Text = beatmapArtist.Text = string.Empty;
- }
- }
-
- private void displayParticipants(User[] value)
- {
- participantInfo.Participants = value;
- }
- }
-}
diff --git a/osu.Game/Screens/Multiplayer/Lobby.cs b/osu.Game/Screens/Multiplayer/Lobby.cs
deleted file mode 100644
index 65fa5fbb16..0000000000
--- a/osu.Game/Screens/Multiplayer/Lobby.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-
-namespace osu.Game.Screens.Multiplayer
-{
- public class Lobby : ScreenWhiteBox
- {
- protected override IEnumerable PossibleChildren => new[] {
- typeof(MatchCreate),
- typeof(Match)
- };
- }
-}
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 7a910574e0..cd9cbe119f 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -2,38 +2,57 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using Microsoft.EntityFrameworkCore.Internal;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
-using OpenTK;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Audio;
-using osu.Framework.Graphics;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
-using osu.Framework.Input;
+using OpenTK;
using OpenTK.Input;
namespace osu.Game.Screens
{
- public abstract class OsuScreen : Screen
+ public abstract class OsuScreen : Screen, IKeyBindingHandler, IHasDescription
{
public BackgroundScreen Background { get; private set; }
+ ///
+ /// A user-facing title for this screen.
+ ///
+ public virtual string Title => GetType().ShortDisplayName();
+
+ public string Description => Title;
+
+ protected virtual bool AllowBackButton => true;
+
///
/// Override to create a BackgroundMode for the current screen.
/// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause.
///
protected virtual BackgroundScreen CreateBackground() => null;
- protected BindableBool ShowOverlays = new BindableBool();
+ private readonly BindableBool hideOverlaysOnEnter = new BindableBool();
///
- /// Whether overlays should be shown when this screen is entered or resumed.
+ /// Whether overlays should be hidden when this screen is entered or resumed.
///
- public virtual bool ShowOverlaysOnEnter => true;
+ protected virtual bool HideOverlaysOnEnter => false;
+
+ private readonly BindableBool allowOpeningOverlays = new BindableBool();
+
+ ///
+ /// Whether overlays should be able to be opened while this screen is active.
+ ///
+ protected virtual bool AllowOpeningOverlays => true;
///
/// Whether this allows the cursor to be displayed.
@@ -84,12 +103,26 @@ namespace osu.Game.Screens
if (osuGame != null)
{
Ruleset.BindTo(osuGame.Ruleset);
- ShowOverlays.BindTo(osuGame.ShowOverlays);
+ hideOverlaysOnEnter.BindTo(osuGame.HideOverlaysOnEnter);
+ allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays);
}
sampleExit = audio.Sample.Get(@"UI/screen-back");
}
+ public bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back && AllowBackButton)
+ {
+ Exit();
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
+
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat || !IsCurrentScreen) return false;
@@ -203,7 +236,8 @@ namespace osu.Game.Screens
if (backgroundParallaxContainer != null)
backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount;
- ShowOverlays.Value = ShowOverlaysOnEnter;
+ hideOverlaysOnEnter.Value = HideOverlaysOnEnter;
+ allowOpeningOverlays.Value = AllowOpeningOverlays;
}
private void onExitingLogo()
diff --git a/osu.Game/Screens/Play/HUD/QuitButton.cs b/osu.Game/Screens/Play/HUD/QuitButton.cs
new file mode 100644
index 0000000000..d0aa0dad92
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/QuitButton.cs
@@ -0,0 +1,198 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input;
+using osu.Framework.MathUtils;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using OpenTK;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ public class QuitButton : FillFlowContainer
+ {
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
+
+ private readonly Button button;
+
+ public Action Action
+ {
+ set => button.Action = value;
+ }
+
+ private readonly OsuSpriteText text;
+
+ public QuitButton()
+ {
+ Direction = FillDirection.Horizontal;
+ Spacing = new Vector2(20, 0);
+ Margin = new MarginPadding(10);
+ Children = new Drawable[]
+ {
+ text = new OsuSpriteText
+ {
+ Text = "hold for menu",
+ Font = @"Exo2.0-Bold",
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft
+ },
+ button = new Button
+ {
+ HoverGained = () => text.FadeIn(500, Easing.OutQuint),
+ HoverLost = () => text.FadeOut(500, Easing.OutQuint)
+ }
+ };
+ AutoSizeAxes = Axes.Both;
+ }
+
+ protected override void LoadComplete()
+ {
+ text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint);
+ base.LoadComplete();
+ }
+
+ private float positionalAdjust;
+
+ protected override bool OnMouseMove(InputState state)
+ {
+ positionalAdjust = Vector2.Distance(state.Mouse.NativeState.Position, button.ScreenSpaceDrawQuad.Centre) / 200;
+ return base.OnMouseMove(state);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered)
+ Alpha = 1;
+ else
+ Alpha = Interpolation.ValueAt(
+ MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000),
+ Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint);
+ }
+
+ private class Button : HoldToConfirmContainer
+ {
+ private SpriteIcon icon;
+ private CircularProgress circularProgress;
+ private Circle overlayCircle;
+
+ protected override bool AllowMultipleFires => true;
+
+ public Action HoverGained;
+ public Action HoverLost;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Size = new Vector2(60);
+
+ Child = new CircularContainer
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Gray1,
+ Alpha = 0.5f,
+ },
+ circularProgress = new CircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ InnerRadius = 1
+ },
+ overlayCircle = new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Gray1,
+ Size = new Vector2(0.9f),
+ },
+ icon = new SpriteIcon
+ {
+ Shadow = false,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(15),
+ Icon = FontAwesome.fa_close
+ },
+ }
+ };
+
+ bind();
+ }
+
+ private void bind()
+ {
+ circularProgress.Current.BindTo(Progress);
+ Progress.ValueChanged += v => icon.Scale = new Vector2(1 + (float)v * 0.2f);
+ }
+
+ private bool pendingAnimation;
+
+ protected override void Confirm()
+ {
+ base.Confirm();
+
+ // temporarily unbind as to not look weird if releasing during confirm animation (can see the unwind of progress).
+ Progress.UnbindAll();
+
+ // avoid starting a new confirm call until we finish animating.
+ pendingAnimation = true;
+
+ Progress.Value = 0;
+
+ overlayCircle.ScaleTo(0, 100)
+ .Then().FadeOut().ScaleTo(1).FadeIn(500)
+ .OnComplete(a =>
+ {
+ icon.ScaleTo(1, 100);
+ circularProgress.FadeOut(100).OnComplete(_ =>
+ {
+ bind();
+
+ circularProgress.FadeIn();
+ pendingAnimation = false;
+ });
+ });
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ HoverGained?.Invoke();
+ return true;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ HoverLost?.Invoke();
+ base.OnHoverLost(state);
+ }
+
+ protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+ {
+ if (!pendingAnimation && state.Mouse.Buttons.Count == 1)
+ BeginConfirm();
+ return true;
+ }
+
+ protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+ {
+ if (state.Mouse.Buttons.Count == 0)
+ AbortConfirm();
+ return true;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 36d8bb75c0..f920b20649 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -34,6 +34,7 @@ namespace osu.Game.Screens.Play
public readonly HealthDisplay HealthDisplay;
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay;
+ public readonly QuitButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
private Bindable showHud;
@@ -51,14 +52,26 @@ namespace osu.Game.Screens.Play
Children = new Drawable[]
{
- KeyCounter = CreateKeyCounter(),
ComboCounter = CreateComboCounter(),
ScoreCounter = CreateScoreCounter(),
AccuracyCounter = CreateAccuracyCounter(),
HealthDisplay = CreateHealthDisplay(),
Progress = CreateProgress(),
ModDisplay = CreateModsContainer(),
- PlayerSettingsOverlay = CreatePlayerSettingsOverlay()
+ PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
+ new FillFlowContainer
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y),
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ KeyCounter = CreateKeyCounter(),
+ HoldToQuit = CreateQuitButton(),
+ }
+ }
}
});
@@ -187,7 +200,6 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
- Y = -TwoLayerButton.SIZE_RETRACTED.Y,
};
protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6)
@@ -205,6 +217,12 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.X,
};
+ protected virtual QuitButton CreateQuitButton() => new QuitButton
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ };
+
protected virtual ModDisplay CreateModsContainer() => new ModDisplay
{
Anchor = Anchor.TopRight,
diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
index a018a2697a..926a96eb6c 100644
--- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
+++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
@@ -1,50 +1,19 @@
// 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 System;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
-using OpenTK.Graphics;
+using osu.Game.Overlays;
namespace osu.Game.Screens.Play
{
- public class HotkeyRetryOverlay : Container, IKeyBindingHandler
+ public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler
{
- public Action Action;
-
- private Box overlay;
-
- private const int activate_delay = 400;
- private const int fadeout_delay = 200;
-
- private bool fired;
-
- [BackgroundDependencyLoader]
- private void load()
- {
- RelativeSizeAxes = Axes.Both;
- AlwaysPresent = true;
-
- Children = new Drawable[]
- {
- overlay = new Box
- {
- Alpha = 0,
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
- }
- };
- }
-
public bool OnPressed(GlobalAction action)
{
if (action != GlobalAction.QuickRetry) return false;
- overlay.FadeIn(activate_delay, Easing.Out);
+ BeginConfirm();
return true;
}
@@ -52,18 +21,8 @@ namespace osu.Game.Screens.Play
{
if (action != GlobalAction.QuickRetry) return false;
- overlay.FadeOut(fadeout_delay, Easing.Out);
+ AbortConfirm();
return true;
}
-
- protected override void Update()
- {
- base.Update();
- if (!fired && overlay.Alpha == 1)
- {
- fired = true;
- Action?.Invoke();
- }
- }
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 83958b2912..9985a24cab 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play
{
protected override float BackgroundParallaxAmount => 0.1f;
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
public Action RestartRequested;
@@ -45,6 +45,8 @@ namespace osu.Game.Screens.Play
public bool AllowLeadIn { get; set; } = true;
public bool AllowResults { get; set; } = true;
+ protected override bool AllowBackButton => false;
+
private Bindable mouseWheelDisabled;
private Bindable userAudioOffset;
@@ -68,7 +70,7 @@ namespace osu.Game.Screens.Play
private SampleChannel sampleRestart;
- private ScoreProcessor scoreProcessor;
+ protected ScoreProcessor ScoreProcessor;
protected RulesetContainer RulesetContainer;
private HUDOverlay hudOverlay;
@@ -93,7 +95,7 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel);
userAudioOffset = config.GetBindable(OsuSetting.AudioOffset);
- Beatmap beatmap;
+ IBeatmap beatmap;
try
{
@@ -107,7 +109,7 @@ namespace osu.Game.Screens.Play
try
{
- RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
+ RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working);
}
catch (BeatmapInvalidForRulesetException)
{
@@ -115,7 +117,7 @@ namespace osu.Game.Screens.Play
// let's try again forcing the beatmap's ruleset.
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
- RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true);
+ RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap);
}
if (!RulesetContainer.Objects.Any())
@@ -147,7 +149,7 @@ namespace osu.Game.Screens.Play
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
userAudioOffset.TriggerChange();
- scoreProcessor = RulesetContainer.CreateScoreProcessor();
+ ScoreProcessor = RulesetContainer.CreateScoreProcessor();
Children = new Drawable[]
{
@@ -162,7 +164,7 @@ namespace osu.Game.Screens.Play
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
},
OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
- Children = new Drawable[]
+ Children = new[]
{
storyboardContainer = new Container
{
@@ -174,6 +176,21 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
+ new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ ProcessCustomClock = false,
+ Breaks = beatmap.Breaks
+ },
+ RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
+ hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
+ {
+ Clock = Clock, // hud overlay doesn't want to use the audio clock directly
+ ProcessCustomClock = false,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ },
new SkipOverlay(firstObjectTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
@@ -181,20 +198,6 @@ namespace osu.Game.Screens.Play
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
},
- hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
- {
- Clock = Clock, // hud overlay doesn't want to use the audio clock directly
- ProcessCustomClock = false,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- },
- new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- ProcessCustomClock = false,
- Breaks = beatmap.Breaks
- }
}
},
failOverlay = new FailOverlay
@@ -216,15 +219,17 @@ namespace osu.Game.Screens.Play
}
};
+ hudOverlay.HoldToQuit.Action = Exit;
+
if (ShowStoryboard)
initializeStoryboard(false);
// Bind ScoreProcessor to ourselves
- scoreProcessor.AllJudged += onCompletion;
- scoreProcessor.Failed += onFail;
+ ScoreProcessor.AllJudged += onCompletion;
+ ScoreProcessor.Failed += onFail;
foreach (var mod in Beatmap.Value.Mods.Value.OfType())
- mod.ApplyToScoreProcessor(scoreProcessor);
+ mod.ApplyToScoreProcessor(ScoreProcessor);
}
private void applyRateFromMods()
@@ -249,7 +254,7 @@ namespace osu.Game.Screens.Play
private void onCompletion()
{
// Only show the completion screen if the player hasn't failed
- if (scoreProcessor.HasFailed || onCompletionEvent != null)
+ if (ScoreProcessor.HasFailed || onCompletionEvent != null)
return;
ValidForResume = false;
@@ -267,7 +272,7 @@ namespace osu.Game.Screens.Play
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = ruleset
};
- scoreProcessor.PopulateScore(score);
+ ScoreProcessor.PopulateScore(score);
score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value;
Push(new Results(score));
});
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 56fbd7b6e7..9c8961498a 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -7,15 +7,15 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
+using osu.Framework.Localisation;
using osu.Framework.Screens;
+using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using OpenTK;
-using osu.Framework.Localisation;
-using osu.Framework.Threading;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.PlayerSettings;
+using OpenTK;
namespace osu.Game.Screens.Play
{
@@ -25,8 +25,8 @@ namespace osu.Game.Screens.Play
private BeatmapMetadataDisplay info;
- private bool showOverlays = true;
- public override bool ShowOverlaysOnEnter => showOverlays;
+ private bool hideOverlays;
+ protected override bool HideOverlaysOnEnter => hideOverlays;
private Task loadTask;
@@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play
player.RestartRequested = () =>
{
- showOverlays = false;
+ hideOverlays = true;
ValidForResume = true;
};
}
@@ -51,11 +51,19 @@ namespace osu.Game.Screens.Play
Origin = Anchor.Centre,
});
- Add(new VisualSettings
+ Add(new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
- Margin = new MarginPadding(25)
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 20),
+ Margin = new MarginPadding(25),
+ Children = new PlayerSettingsGroup[]
+ {
+ new VisualSettings(),
+ new InputSettings()
+ }
});
loadTask = LoadComponentAsync(player);
diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs
new file mode 100644
index 0000000000..755ba468cc
--- /dev/null
+++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs
@@ -0,0 +1,30 @@
+// 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.Graphics;
+using osu.Game.Configuration;
+
+namespace osu.Game.Screens.Play.PlayerSettings
+{
+ public class InputSettings : PlayerSettingsGroup
+ {
+ protected override string Title => "Input settings";
+
+ private readonly PlayerCheckbox mouseButtonsCheckbox;
+
+ public InputSettings()
+ {
+ Children = new Drawable[]
+ {
+ mouseButtonsCheckbox = new PlayerCheckbox
+ {
+ LabelText = "Disable mouse buttons"
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config) => mouseButtonsCheckbox.Bindable = config.GetBindable(OsuSetting.MouseDisableButtons);
+ }
+}
diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs
index 5cc4b30950..b79c212ade 100644
--- a/osu.Game/Screens/Play/SongProgressInfo.cs
+++ b/osu.Game/Screens/Play/SongProgressInfo.cs
@@ -85,11 +85,13 @@ namespace osu.Game.Screens.Play
if (currentSecond != previousSecond && songCurrentTime < songLength)
{
- timeCurrent.Text = TimeSpan.FromSeconds(currentSecond).ToString(songCurrentTime < 0 ? @"\-m\:ss" : @"m\:ss");
- timeLeft.Text = TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime).ToString(@"\-m\:ss");
+ timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
+ timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime));
previousSecond = currentSecond;
}
}
+
+ private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}";
}
}
diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs
index 32161a0b8e..7cbd2e4403 100644
--- a/osu.Game/Screens/Ranking/Results.cs
+++ b/osu.Game/Screens/Ranking/Results.cs
@@ -57,6 +57,7 @@ namespace osu.Game.Screens.Ranking
{
base.OnEntering(last);
(Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint);
+ Background.ScaleTo(1.1f, transition_time, Easing.OutQuint);
allCircles.ForEach(c =>
{
@@ -102,6 +103,8 @@ namespace osu.Game.Screens.Ranking
c.ScaleTo(0, transition_time, Easing.OutSine);
});
+ Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint);
+
Content.FadeOut(transition_time / 4);
return base.OnExiting(next);
@@ -160,7 +163,6 @@ namespace osu.Game.Screens.Ranking
{
RelativeSizeAxes = Axes.Both,
ParallaxAmount = 0.01f,
- Scale = new Vector2(1 / circle_outer_scale / overscan),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index f005261ffa..97f6371cb2 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -4,9 +4,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using JetBrains.Annotations;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -21,6 +23,8 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Select
{
@@ -28,6 +32,8 @@ namespace osu.Game.Screens.Select
{
private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0);
+ private readonly IBindable ruleset = new Bindable();
+
protected BufferedWedgeInfo Info;
public BeatmapInfoWedge()
@@ -46,6 +52,14 @@ namespace osu.Game.Screens.Select
};
}
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] OsuGame osuGame)
+ {
+ if (osuGame != null)
+ ruleset.BindTo(osuGame.Ruleset);
+ ruleset.ValueChanged += updateRuleset;
+ }
+
protected override bool BlockPassThroughMouse => false;
protected override void PopIn()
@@ -62,19 +76,39 @@ namespace osu.Game.Screens.Select
this.FadeOut(500, Easing.In);
}
+ private WorkingBeatmap beatmap;
+
public void UpdateBeatmap(WorkingBeatmap beatmap)
{
- LoadComponentAsync(new BufferedWedgeInfo(beatmap)
- {
- Shear = -Shear,
- Depth = Info?.Depth + 1 ?? 0,
- }, newInfo =>
+ this.beatmap = beatmap;
+ loadBeatmap();
+ }
+
+ private void updateRuleset(RulesetInfo ruleset) => loadBeatmap();
+
+ private void loadBeatmap()
+ {
+ void updateState()
{
State = beatmap == null ? Visibility.Hidden : Visibility.Visible;
Info?.FadeOut(250);
Info?.Expire();
+ }
+ if (beatmap == null)
+ {
+ updateState();
+ return;
+ }
+
+ LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value)
+ {
+ Shear = -Shear,
+ Depth = Info?.Depth + 1 ?? 0,
+ }, newInfo =>
+ {
+ updateState();
Add(Info = newInfo);
});
}
@@ -90,9 +124,13 @@ namespace osu.Game.Screens.Select
private UnicodeBindableString titleBinding;
private UnicodeBindableString artistBinding;
- public BufferedWedgeInfo(WorkingBeatmap working)
+ private readonly RulesetInfo ruleset;
+
+ public BufferedWedgeInfo(WorkingBeatmap working, RulesetInfo userRuleset)
{
this.working = working;
+
+ ruleset = userRuleset ?? working.BeatmapInfo.Ruleset;
}
[BackgroundDependencyLoader]
@@ -211,11 +249,10 @@ namespace osu.Game.Screens.Select
private InfoLabel[] getInfoLabels()
{
var beatmap = working.Beatmap;
- var info = working.BeatmapInfo;
List labels = new List();
- if (beatmap?.HitObjects?.Count > 0)
+ if (beatmap?.HitObjects?.Any() == true)
{
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
@@ -224,7 +261,7 @@ namespace osu.Game.Screens.Select
{
Name = "Length",
Icon = FontAwesome.fa_clock_o,
- Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
+ Content = TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic
@@ -234,14 +271,26 @@ namespace osu.Game.Screens.Select
Content = getBPMRange(beatmap),
}));
- //get statistics from the current ruleset.
- labels.AddRange(info.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s)));
+ IBeatmap playableBeatmap;
+
+ try
+ {
+ // Try to get the beatmap with the user's ruleset
+ playableBeatmap = working.GetPlayableBeatmap(ruleset);
+ }
+ catch (BeatmapInvalidForRulesetException)
+ {
+ // Can't be converted to the user's ruleset, so use the beatmap's own ruleset
+ playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset);
+ }
+
+ labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
}
return labels.ToArray();
}
- private string getBPMRange(Beatmap beatmap)
+ private string getBPMRange(IBeatmap beatmap)
{
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index ee458a13a4..f9f3db3827 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -190,7 +190,5 @@ namespace osu.Game.Screens.Select
protected override bool OnMouseMove(InputState state) => true;
protected override bool OnClick(InputState state) => true;
-
- protected override bool OnDragStart(InputState state) => true;
}
}
diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs
index 363249ab63..8f07e0a763 100644
--- a/osu.Game/Screens/Select/Footer.cs
+++ b/osu.Game/Screens/Select/Footer.cs
@@ -141,7 +141,5 @@ namespace osu.Game.Screens.Select
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnClick(InputState state) => true;
-
- protected override bool OnDragStart(InputState state) => true;
}
}
diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 4e252eac75..3ffac591f3 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -7,7 +7,12 @@ namespace osu.Game.Screens.Select
{
protected override bool OnSelectionFinalised()
{
- Exit();
+ Schedule(() =>
+ {
+ // needs to be scheduled else we enter an infinite feedback loop.
+ if (IsCurrentScreen) Exit();
+ });
+
return true;
}
}
diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs
index 1ef0b6cca0..29301899d5 100644
--- a/osu.Game/Screens/Tournament/Drawings.cs
+++ b/osu.Game/Screens/Tournament/Drawings.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.Tournament
{
private const string results_filename = "drawings_results.txt";
- public override bool ShowOverlaysOnEnter => false;
+ protected override bool HideOverlaysOnEnter => true;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 2850de8ba5..7470f6ebed 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -10,6 +10,7 @@ using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Beatmaps
@@ -79,9 +80,12 @@ namespace osu.Game.Tests.Beatmaps
{
var beatmap = getBeatmap(name);
+ var rulesetInstance = CreateRuleset();
+ beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
+
var result = new ConvertResult();
- var converter = CreateConverter(beatmap);
+ var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
converter.ObjectConverted += (orig, converted) =>
{
converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
@@ -92,7 +96,7 @@ namespace osu.Game.Tests.Beatmaps
result.Mappings.Add(mapping);
};
- converter.Convert(beatmap);
+ converter.Convert();
return result;
}
@@ -107,7 +111,7 @@ namespace osu.Game.Tests.Beatmaps
}
}
- private Beatmap getBeatmap(string name)
+ private IBeatmap getBeatmap(string name)
{
using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
using (var stream = new StreamReader(resStream))
@@ -125,7 +129,7 @@ namespace osu.Game.Tests.Beatmaps
}
protected abstract IEnumerable CreateConvertValue(HitObject hitObject);
- protected abstract IBeatmapConverter CreateConverter(Beatmap beatmap);
+ protected abstract Ruleset CreateRuleset();
private class ConvertMapping
{
diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
index 09a3a7af8c..6bad08baaa 100644
--- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
@@ -12,8 +12,14 @@ namespace osu.Game.Tests.Beatmaps
public class TestBeatmap : Beatmap
{
public TestBeatmap(RulesetInfo ruleset)
- : base(createTestBeatmap())
{
+ var baseBeatmap = createTestBeatmap();
+
+ BeatmapInfo = baseBeatmap.BeatmapInfo;
+ ControlPointInfo = baseBeatmap.ControlPointInfo;
+ Breaks = baseBeatmap.Breaks;
+ HitObjects = baseBeatmap.HitObjects;
+
BeatmapInfo.Ruleset = ruleset;
}
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index e24fbab3ac..37693c99e8 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -17,14 +17,14 @@ namespace osu.Game.Tests.Beatmaps
{
}
- public TestWorkingBeatmap(Beatmap beatmap)
+ public TestWorkingBeatmap(IBeatmap beatmap)
: base(beatmap.BeatmapInfo)
{
this.beatmap = beatmap;
}
- private readonly Beatmap beatmap;
- protected override Beatmap GetBeatmap() => beatmap;
+ private readonly IBeatmap beatmap;
+ protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetTrack()
diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs
index 43b20f7021..85d3684530 100644
--- a/osu.Game/Tests/Visual/EditorClockTestCase.cs
+++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual
protected EditorClockTestCase()
{
- Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false };
+ Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
index 29132258c2..51460ecb6d 100644
--- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -259,9 +259,9 @@ namespace osu.Game.Tests.Visual
private readonly OsuSpriteText text;
private readonly Score score;
- private readonly Beatmap beatmap;
+ private readonly IBeatmap beatmap;
- public PerformanceDisplay(Score score, Beatmap beatmap)
+ public PerformanceDisplay(Score score, IBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index 5ed43b2814..bf6236e4d5 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual
{
Player p = null;
AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset));
- AddUntilStep(() => p.IsLoaded);
+ AddUntilStep(() => ContinueCondition(p));
}
else
{
@@ -52,12 +52,14 @@ namespace osu.Game.Tests.Visual
{
Player p = null;
AddStep(r.Name, () => p = loadPlayerFor(r));
- AddUntilStep(() => p.IsLoaded);
+ AddUntilStep(() => ContinueCondition(p));
}
}
}
- protected virtual Beatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
+ protected virtual bool ContinueCondition(Player player) => player.IsLoaded;
+
+ protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1a75f1979a..afb656a260 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -17,7 +17,7 @@
-
+