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..eb076a3301 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed
+Subproject commit eb076a3301231eb73917073499051e49a9b12978
diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs
index 16bbf90cd4..a1c2a8aef2 100644
--- a/osu.Desktop.Deploy/Program.cs
+++ b/osu.Desktop.Deploy/Program.cs
@@ -19,7 +19,7 @@ namespace osu.Desktop.Deploy
{
private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
private static string nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe");
- private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe");
+ private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.8.0\tools\Squirrel.exe");
private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe";
public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"];
@@ -115,7 +115,7 @@ namespace osu.Desktop.Deploy
checkReleaseFiles();
write("Running squirrel build...");
- runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi");
+ runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --framework-version=net471 --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi");
//prune again to clean up before upload.
pruneReleases();
diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
index a97b8197b4..063fb89918 100644
--- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
+++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 27bc3f7597..3d64cab84e 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,4 +1,4 @@
-
+
net471;netcoreapp2.0
@@ -10,8 +10,8 @@
osu!lazer
osu!lazer
lazer.ico
- 0.0.0.0
- 0.0.0.0
+ 0.0.0
+ 0.0.0
$(DefineConstants);NET_FRAMEWORK
@@ -31,9 +31,9 @@
-
+
-
\ 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/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..f40ef67dfb 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;
@@ -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/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..070dc19a6f 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,10 +28,6 @@ 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);
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..e6e3028d62
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -0,0 +1,126 @@
+// 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)
+ {
+ double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / 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..7f3985b26d 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 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/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index f8b2311a13..8791e8ed86 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -99,6 +99,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 +204,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 +223,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/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.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 75%
rename from osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
rename to osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 8f0feca207..eeb776fa6e 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,9 +52,9 @@ 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
@@ -58,8 +69,12 @@ namespace osu.Game.Rulesets.Osu.Scoring
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);
+
+ double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate;
+ double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
+ realOverallDifficulty = (80 - 0.5 - 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 +100,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 +139,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 +152,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 +172,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 +190,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 +200,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 +213,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/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..9c9cd1f0fb
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -0,0 +1,111 @@
+// 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()
+ {
+ double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / 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..f60caf2397 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -275,13 +275,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/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/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/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/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index dad8fb8fed..d3d21509fd 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,6 +26,19 @@ namespace osu.Game.Tests.Visual
[Description("mod select and icon display")]
public class TestCaseMods : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ModSelectOverlay),
+ typeof(ModDisplay),
+ typeof(ModSection),
+ typeof(ModIcon),
+ typeof(ModButton),
+ typeof(ModButtonEmpty),
+ typeof(DifficultyReductionSection),
+ typeof(DifficultyIncreaseSection),
+ typeof(SpecialSection),
+ };
+
private const string unranked_suffix = " (Unranked)";
private RulesetStore rulesets;
@@ -66,7 +81,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;
diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
new file mode 100644
index 0000000000..af51a6221f
--- /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;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMultiHeader : OsuTestCase
+ {
+ public TestCaseMultiHeader()
+ {
+ Lobby lobby;
+ Children = new Drawable[]
+ {
+ lobby = new Lobby
+ {
+ Padding = new MarginPadding { Top = Header.HEIGHT },
+ },
+ new Header(lobby),
+ };
+ }
+ }
+}
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..f0f8d41074
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseQuitButton.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 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();
+
+ // initial display
+ AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
+ AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
+
+ 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.ButtonDown(MouseButton.Left);
+ });
+
+ AddStep("Early release", () => InputManager.ButtonUp(MouseButton.Left));
+ AddAssert("action not triggered", () => !exitAction);
+
+ AddStep("Trigger exit action", () => InputManager.ButtonDown(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..7a743655f4
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -0,0 +1,145 @@
+// 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 Title { get; }
+ protected abstract string NextTitle { get; }
+ protected abstract TestScreen CreateNextScreen();
+
+ public override string ToString() => Title;
+
+ 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
+ {
+ protected override string Title => @"Screen One";
+ protected override string NextTitle => @"Two";
+ protected override TestScreen CreateNextScreen() => new TestScreenTwo();
+ }
+
+ private class TestScreenTwo : TestScreen
+ {
+ protected 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..36fde8a319 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;
@@ -333,7 +335,7 @@ 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();
@@ -341,9 +343,16 @@ namespace osu.Game.Beatmaps
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 +360,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 366b4f163f..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);
@@ -373,17 +375,18 @@ namespace osu.Game.Beatmaps.Formats
if (parser == null)
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
- var obj = parser.Parse(line);
+ var obj = parser.Parse(line, getOffsetTime());
if (obj != null)
{
- obj.StartTime = getOffsetTime(obj.StartTime);
beatmap.HitObjects.Add(obj);
}
}
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
+ private double getOffsetTime() => ApplyOffsets ? offset : 0;
+
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
}
}
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
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/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/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/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 95abc4edb3..9edd0f1f34 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
public abstract class ConvertHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text)
+ {
+ return Parse(text, 0);
+ }
+
+ public HitObject Parse(string text, double offset)
{
try
{
@@ -146,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
else if ((type & ConvertHitObjectType.Spinner) > 0)
{
- result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture));
+ result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + offset);
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
@@ -164,13 +169,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
}
- result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime);
+ result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime + offset);
}
if (result == null)
throw new InvalidOperationException($@"Unknown hit object type {type}.");
- result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
+ result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + offset;
result.Samples = convertSoundType(soundType, bankInfo);
return result;
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..b657fe5597 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;
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..88a253d719
--- /dev/null
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
@@ -0,0 +1,276 @@
+// 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;
+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.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
+ {
+ private const float corner_radius = 5;
+ private const float selection_border_width = 4;
+ 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 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 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,
+ };
+
+ Action += () => State = SelectionState.Selected;
+ }
+
+ [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), 100);
+ };
+
+ 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);
+ }
+ }
+}
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..db8898495f
--- /dev/null
+++ b/osu.Game/Screens/Multi/Header.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 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 OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Multi
+{
+ public class Header : Container
+ {
+ public const float HEIGHT = 121;
+
+ private readonly OsuSpriteText screenTitle;
+ 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,
+ },
+ screenTitle = 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 => screenTitle.Text = s.ToString();
+ breadcrumbs.Current.TriggerChange();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ screenTitle.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..b3d393209c
--- /dev/null
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -0,0 +1,100 @@
+// 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.Multi.Screens;
+
+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,
+ };
+
+ Lobby lobby;
+ 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 = lobby = new Lobby(),
+ },
+ new Header(lobby),
+ };
+
+ lobby.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();
+ }
+
+ 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/Multiplayer/Lobby.cs b/osu.Game/Screens/Multi/Screens/Lobby.cs
similarity index 90%
rename from osu.Game/Screens/Multiplayer/Lobby.cs
rename to osu.Game/Screens/Multi/Screens/Lobby.cs
index 65fa5fbb16..dcda40e0d7 100644
--- a/osu.Game/Screens/Multiplayer/Lobby.cs
+++ b/osu.Game/Screens/Multi/Screens/Lobby.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi.Screens
{
public class Lobby : ScreenWhiteBox
{
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/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/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 7a910574e0..a188b7aa64 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -3,37 +3,48 @@
using System;
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
{
public BackgroundScreen Background { get; private set; }
+ 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 +95,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 +228,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..0150d76251 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;
@@ -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())
@@ -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,13 +176,14 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
- new SkipOverlay(firstObjectTime)
+ new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
{
- Clock = Clock, // skip button doesn't want to use the audio clock directly
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
ProcessCustomClock = false,
- AdjustableClock = adjustableClock,
- FramedClock = offsetClock,
+ 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
@@ -188,13 +191,13 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
- new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
+ new SkipOverlay(firstObjectTime)
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,
- Breaks = beatmap.Breaks
- }
+ AdjustableClock = adjustableClock,
+ FramedClock = offsetClock,
+ },
}
},
failOverlay = new FailOverlay
@@ -216,6 +219,8 @@ namespace osu.Game.Screens.Play
}
};
+ hudOverlay.HoldToQuit.Action = Exit;
+
if (ShowStoryboard)
initializeStoryboard(false);
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 56fbd7b6e7..734837a4f1 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -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;
};
}
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/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/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..bda438d906 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual
}
}
- protected virtual Beatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
+ protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index 22bc9ed1a0..1584605166 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -12,12 +12,13 @@ namespace osu.Game.Users
public abstract Color4 GetAppropriateColour(OsuColour colours);
}
- public abstract class UserStatusAvailable : UserStatus
+ public class UserStatusOnline : UserStatus
{
+ public override string Message => @"Online";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.BlueDarker;
}
- public abstract class UserStatusBusy : UserStatus
+ public abstract class UserStatusBusy : UserStatusOnline
{
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark;
}
@@ -28,17 +29,12 @@ namespace osu.Game.Users
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
}
- public class UserStatusOnline : UserStatusAvailable
- {
- public override string Message => @"Online";
- }
-
- public class UserStatusSpectating : UserStatusAvailable
+ public class UserStatusSpectating : UserStatusOnline
{
public override string Message => @"Spectating a game";
}
- public class UserStatusInLobby : UserStatusAvailable
+ public class UserStatusInLobby : UserStatusOnline
{
public override string Message => @"in Multiplayer Lobby";
}
@@ -53,13 +49,13 @@ namespace osu.Game.Users
public override string Message => @"Multiplaying";
}
- public class UserStatusModding : UserStatus
+ public class UserStatusModding : UserStatusOnline
{
public override string Message => @"Modding a map";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
}
- public class UserStatusDoNotDisturb : UserStatus
+ public class UserStatusDoNotDisturb : UserStatusBusy
{
public override string Message => @"Do not disturb";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;