diff --git a/appveyor.yml b/appveyor.yml
index 9048428590..9cf68803a2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,17 +9,17 @@ cache:
- inspectcode -> appveyor.yml
- packages -> **\packages.config
install:
- - cmd: git submodule update --init --recursive
+ - 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.3/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- - cmd: nuget restore
+ - cmd: nuget restore -verbosity quiet
build:
project: osu.sln
parallel: true
verbosity: minimal
after_build:
- - cmd: inspectcode /o="inspectcodereport.xml" /caches-home="inspectcode" osu.sln
+ - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
\ No newline at end of file
diff --git a/osu-framework b/osu-framework
index d87dab204b..4fc866eee3 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit d87dab204b3df50f62e6070b1970c135ea647d78
+Subproject commit 4fc866eee3803f88b155150e32e021b9c21e647f
diff --git a/osu-resources b/osu-resources
index 1750ab8f67..4287ee8043 160000
--- a/osu-resources
+++ b/osu-resources
@@ -1 +1 @@
-Subproject commit 1750ab8f6761ab35592fd46da71fbe0c141bfd93
+Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f
diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config
index 2fae7a5e1c..2fbea810f6 100644
--- a/osu.Desktop.Deploy/App.config
+++ b/osu.Desktop.Deploy/App.config
@@ -13,7 +13,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
-
+
diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs
index 54fb50d0f8..e90fb1e567 100644
--- a/osu.Desktop.Deploy/Program.cs
+++ b/osu.Desktop.Deploy/Program.cs
@@ -145,6 +145,8 @@ namespace osu.Desktop.Deploy
///
private static void checkReleaseFiles()
{
+ if (!canGitHub) return;
+
var releaseLines = getReleaseLines();
//ensure we have all files necessary
@@ -157,6 +159,8 @@ namespace osu.Desktop.Deploy
private static void pruneReleases()
{
+ if (!canGitHub) return;
+
write("Pruning RELEASES...");
var releaseLines = getReleaseLines().ToList();
@@ -190,7 +194,7 @@ namespace osu.Desktop.Deploy
private static void uploadBuild(string version)
{
- if (string.IsNullOrEmpty(GitHubAccessToken) || string.IsNullOrEmpty(codeSigningCertPath))
+ if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
return;
write("Publishing to GitHub...");
@@ -228,8 +232,12 @@ namespace osu.Desktop.Deploy
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
+ private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
+
private static void checkGitHubReleases()
{
+ if (!canGitHub) return;
+
write("Checking GitHub releases...");
var req = new JsonWebRequest>($"{GitHubApiEndpoint}");
req.AuthenticatedBlockingPerform();
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 0e4935aa7a..6b9ec8b9a4 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -11,14 +11,14 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
- internal class CatchBeatmapConverter : BeatmapConverter
+ internal class CatchBeatmapConverter : BeatmapConverter
{
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
- protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
{
var curveData = obj as IHasCurve;
- var positionData = obj as IHasPosition;
+ var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo;
if (positionData == null)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 7fac19d135..b2f7fdabfc 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -6,9 +6,9 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
- internal class CatchBeatmapProcessor : BeatmapProcessor
+ internal class CatchBeatmapProcessor : BeatmapProcessor
{
- public override void PostProcess(Beatmap beatmap)
+ public override void PostProcess(Beatmap beatmap)
{
if (beatmap.ComboColors.Count == 0)
return;
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int comboIndex = 0;
int colourIndex = 0;
- CatchBaseHit lastObj = null;
+ CatchHitObject lastObj = null;
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
index b77be9d1f0..e9524a867d 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
@@ -8,14 +8,14 @@ using System.Collections.Generic;
namespace osu.Game.Rulesets.Catch
{
- public class CatchDifficultyCalculator : DifficultyCalculator
+ public class CatchDifficultyCalculator : DifficultyCalculator
{
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
- public override double Calculate(Dictionary categoryDifficulty = null) => 0;
+ public override double Calculate(Dictionary categoryDifficulty = null) => 0;
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+ protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
similarity index 55%
rename from osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
rename to osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 2f33cf1093..cb4e6453ce 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -1,14 +1,18 @@
// Copyright (c) 2007-2017 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.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
- public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo
+ public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
{
+ public const double OBJECT_RADIUS = 44;
+
public float X { get; set; }
public Color4 ComboColour { get; set; } = Color4.Gray;
@@ -20,5 +24,14 @@ namespace osu.Game.Rulesets.Catch.Objects
/// The next fruit starts a new combo. Used for explodey.
///
public virtual bool LastInCombo { get; set; }
+
+ public float Scale { get; set; } = 1;
+
+ public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ {
+ base.ApplyDefaults(controlPointInfo, difficulty);
+
+ Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index e057bf3d8e..b90a06b94e 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -5,11 +5,12 @@ using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public abstract class DrawableCatchHitObject : DrawableCatchHitObject
- where TObject : CatchBaseHit
+ where TObject : CatchHitObject
{
public new TObject HitObject;
@@ -17,12 +18,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(hitObject)
{
HitObject = hitObject;
+
+ Scale = new Vector2(HitObject.Scale);
}
}
- public abstract class DrawableCatchHitObject : DrawableScrollingHitObject
+ public abstract class DrawableCatchHitObject : DrawableScrollingHitObject
{
- protected DrawableCatchHitObject(CatchBaseHit hitObject)
+ protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject)
{
RelativePositionAxes = Axes.Both;
@@ -30,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Y = (float)HitObject.StartTime;
}
- public Func CheckPosition;
+ public Func CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
index afda91d0b4..bfb674d1b4 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
- foreach (CatchBaseHit tick in s.Ticks)
+ foreach (CatchHitObject tick in s.Ticks)
{
TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null)
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNested(DrawableHitObject h)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index 00ddd365e3..2de266b3f0 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{
public class Pulp : Circle, IHasAccentColour
{
- public const float PULP_SIZE = 20;
+ public const float PULP_SIZE = (float)CatchHitObject.OBJECT_RADIUS / 2.2f;
public Pulp()
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
index b1206e0d75..a2bdf830e5 100644
--- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Droplet : CatchBaseHit
+ public class Droplet : CatchHitObject
{
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
index fc55f83969..5f1060fb51 100644
--- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Fruit : CatchBaseHit
+ public class Fruit : CatchHitObject
{
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 6462f6f6a8..bf9f0bd44b 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -15,7 +15,7 @@ using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class JuiceStream : CatchBaseHit, IHasCurve
+ public class JuiceStream : CatchHitObject, IHasCurve
{
///
/// Positional distance that results in a duration of one second, before any speed adjustments.
@@ -42,11 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
- public IEnumerable Ticks
+ public IEnumerable Ticks
{
get
{
- SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime));
+ SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime));
if (TickDistance == 0)
return ticks;
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 66a5636b74..0806c4b29d 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -10,14 +10,14 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
- internal class CatchScoreProcessor : ScoreProcessor
+ internal class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(RulesetContainer rulesetContainer)
+ public CatchScoreProcessor(RulesetContainer rulesetContainer)
: base(rulesetContainer)
{
}
- protected override void SimulateAutoplay(Beatmap beatmap)
+ protected override void SimulateAutoplay(Beatmap beatmap)
{
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
index a890a8a386..586de17f15 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
@@ -11,16 +11,26 @@ namespace osu.Game.Rulesets.Catch.Tests
[Ignore("getting CI working")]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{
- public TestCaseCatchStacker() : base(typeof(CatchRuleset))
+ public TestCaseCatchStacker()
+ : base(typeof(CatchRuleset))
{
}
protected override Beatmap CreateBeatmap()
{
- var beatmap = new Beatmap();
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 6,
+ }
+ }
+ };
- for (int i = 0; i < 256; i++)
- beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
+ for (int i = 0; i < 512; i++)
+ beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
deleted file mode 100644
index 341612b760..0000000000
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Tests.Visual;
-using OpenTK;
-
-namespace osu.Game.Rulesets.Catch.Tests
-{
- [TestFixture]
- [Ignore("getting CI working")]
- internal class TestCaseCatcher : OsuTestCase
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Catcher),
- };
-
- [BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
- {
- Children = new Drawable[]
- {
- new CatchInputManager(rulesets.GetRuleset(2))
- {
- RelativeSizeAxes = Axes.Both,
- Child = new Catcher
- {
- RelativePositionAxes = Axes.Both,
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Size = new Vector2(1, 0.2f),
- }
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
new file mode 100644
index 0000000000..538f6930ed
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
@@ -0,0 +1,50 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ [Ignore("getting CI working")]
+ internal class TestCaseCatcherArea : OsuTestCase
+ {
+ private RulesetInfo catchRuleset;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea),
+ };
+
+ public TestCaseCatcherArea()
+ {
+ AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
+ }
+
+ private void createCatcher(float size)
+ {
+ Child = new CatchInputManager(catchRuleset)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CatcherArea(new BeatmapDifficulty { CircleSize = size })
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.BottomLeft
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ catchRuleset = rulesets.GetRuleset(2);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..0d2dc14160
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new CatchRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 987eef5e45..6fd0793500 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -1,11 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
@@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI
protected override Container Content => content;
private readonly Container content;
- private readonly Container catcherContainer;
- private readonly Catcher catcher;
+ private readonly CatcherArea catcherArea;
- public CatchPlayfield()
+ public CatchPlayfield(BeatmapDifficulty difficulty)
: base(Axes.Y)
{
Container explodingFruitContainer;
@@ -43,30 +42,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
RelativeSizeAxes = Axes.Both,
},
- catcherContainer = new Container
+ catcherArea = new CatcherArea(difficulty)
{
- RelativeSizeAxes = Axes.X,
+ ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
- Height = 180,
- Child = catcher = new Catcher
- {
- ExplodingFruitTarget = explodingFruitContainer,
- RelativePositionAxes = Axes.Both,
- Origin = Anchor.TopCentre,
- X = 0.5f,
- }
}
};
}
- protected override void Update()
- {
- base.Update();
- catcher.Size = new Vector2(catcherContainer.DrawSize.Y);
- }
-
- public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
+ public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.CanCatch(obj);
public override void Add(DrawableHitObject h)
{
@@ -88,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.UI
(judgedObject.Parent as Container)?.Remove(judgedObject);
(judgedObject.Parent as Container)?.Remove(judgedObject);
- catcher.Add(judgedObject, screenPosition);
+ catcherArea.Add(judgedObject, screenPosition);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 92912eb177..3ed9090098 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.UI
{
- public class CatchRulesetContainer : ScrollingRulesetContainer
+ public class CatchRulesetContainer : ScrollingRulesetContainer
{
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset)
@@ -22,15 +22,15 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
- protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
+ protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
- protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter();
+ protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter();
- protected override Playfield CreatePlayfield() => new CatchPlayfield();
+ protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h)
+ protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h)
{
var fruit = h as Fruit;
if (fruit != null)
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
deleted file mode 100644
index 87fe95ed2f..0000000000
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.Input.Bindings;
-using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using OpenTK;
-
-namespace osu.Game.Rulesets.Catch.UI
-{
- public class Catcher : Container, IKeyBindingHandler
- {
- private Texture texture;
-
- private Container caughtFruit;
-
- public Container ExplodingFruitTarget;
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
-
- Children = new Drawable[]
- {
- createCatcherSprite(),
- caughtFruit = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
- }
- };
- }
-
- private int currentDirection;
-
- private bool dashing;
-
- protected bool Dashing
- {
- get { return dashing; }
- set
- {
- if (value == dashing) return;
-
- dashing = value;
-
- if (dashing)
- Schedule(addAdditiveSprite);
- }
- }
-
- private void addAdditiveSprite()
- {
- if (!dashing) return;
-
- var additive = createCatcherSprite();
-
- additive.RelativePositionAxes = Axes.Both;
- additive.Blending = BlendingMode.Additive;
- additive.Position = Position;
- additive.Scale = Scale;
-
- ((Container)Parent).Add(additive);
-
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
-
- Scheduler.AddDelayed(addAdditiveSprite, 50);
- }
-
- private Sprite createCatcherSprite() => new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Texture = texture,
- OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
- };
-
- public bool OnPressed(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection--;
- return true;
- case CatchAction.MoveRight:
- currentDirection++;
- return true;
- case CatchAction.Dash:
- Dashing = true;
- return true;
- }
-
- return false;
- }
-
- public bool OnReleased(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection++;
- return true;
- case CatchAction.MoveRight:
- currentDirection--;
- return true;
- case CatchAction.Dash:
- Dashing = false;
- return true;
- }
-
- return false;
- }
-
- ///
- /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
- ///
- private const double base_speed = 1.0 / 512;
-
- protected override void Update()
- {
- base.Update();
-
- if (currentDirection == 0) return;
-
- double dashModifier = Dashing ? 1 : 0.5;
-
- Scale = new Vector2(Math.Sign(currentDirection), 1);
- X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
- }
-
- public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
- {
- fruit.RelativePositionAxes = Axes.None;
- fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
-
- fruit.Anchor = Anchor.TopCentre;
- fruit.Origin = Anchor.BottomCentre;
- fruit.Scale *= 0.7f;
- fruit.LifetimeEnd = double.MaxValue;
-
- float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
-
- while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
- {
- fruit.X += RNG.Next(-5, 5);
- fruit.Y -= RNG.Next(0, 5);
- }
-
- caughtFruit.Add(fruit);
-
- if (((CatchBaseHit)fruit.HitObject).LastInCombo)
- explode();
- }
-
- private void explode()
- {
- var fruit = caughtFruit.ToArray();
-
- foreach (var f in fruit)
- {
- var originalX = f.X * Scale.X;
-
- if (ExplodingFruitTarget != null)
- {
- f.Anchor = Anchor.TopLeft;
- f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
-
- caughtFruit.Remove(f);
-
- ExplodingFruitTarget.Add(f);
- }
-
- f.MoveToY(f.Y - 50, 250, Easing.OutSine)
- .Then()
- .MoveToY(f.Y + 50, 500, Easing.InSine);
-
- f.MoveToX(f.X + originalX * 6, 1000);
- f.FadeOut(750);
-
- f.Expire();
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
new file mode 100644
index 0000000000..203db1bb8c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -0,0 +1,240 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Bindings;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherArea : Container
+ {
+ public const float CATCHER_SIZE = 172;
+
+ private readonly Catcher catcher;
+
+ public Container ExplodingFruitTarget
+ {
+ set { catcher.ExplodingFruitTarget = value; }
+ }
+
+ public CatcherArea(BeatmapDifficulty difficulty = null)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = CATCHER_SIZE;
+ Child = catcher = new Catcher(difficulty)
+ {
+ AdditiveTarget = this,
+ };
+ }
+
+ public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
+ {
+ fruit.RelativePositionAxes = Axes.None;
+ fruit.Position = new Vector2(catcher.ToLocalSpace(absolutePosition).X - catcher.DrawSize.X / 2, 0);
+
+ fruit.Anchor = Anchor.TopCentre;
+ fruit.Origin = Anchor.BottomCentre;
+ fruit.Scale *= 0.7f;
+ fruit.LifetimeEnd = double.MaxValue;
+
+ catcher.Add(fruit);
+ }
+
+ public bool CanCatch(CatchHitObject obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X * Math.Abs(catcher.Scale.X) / DrawSize.X / 2;
+
+ public class Catcher : Container, IKeyBindingHandler
+ {
+ private Texture texture;
+
+ private Container caughtFruit;
+
+ public Container ExplodingFruitTarget;
+
+ public Container AdditiveTarget;
+
+ public Catcher(BeatmapDifficulty difficulty = null)
+ {
+ RelativePositionAxes = Axes.X;
+ X = 0.5f;
+
+ Origin = Anchor.TopCentre;
+ Anchor = Anchor.TopLeft;
+
+ Size = new Vector2(CATCHER_SIZE);
+ if (difficulty != null)
+ Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
+
+ Children = new Drawable[]
+ {
+ createCatcherSprite(),
+ caughtFruit = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ }
+ };
+ }
+
+ private int currentDirection;
+
+ private bool dashing;
+
+ protected bool Dashing
+ {
+ get { return dashing; }
+ set
+ {
+ if (value == dashing) return;
+
+ dashing = value;
+
+ if (dashing)
+ Schedule(addAdditiveSprite);
+ }
+ }
+
+ private void addAdditiveSprite()
+ {
+ if (!dashing || AdditiveTarget == null) return;
+
+ var additive = createCatcherSprite();
+
+ additive.Anchor = Anchor;
+ additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
+ additive.Position = Position;
+ additive.Scale = Scale;
+ additive.RelativePositionAxes = RelativePositionAxes;
+ additive.Blending = BlendingMode.Additive;
+
+ AdditiveTarget.Add(additive);
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+
+ Scheduler.AddDelayed(addAdditiveSprite, 50);
+ }
+
+ private Sprite createCatcherSprite() => new Sprite
+ {
+ Size = new Vector2(CATCHER_SIZE),
+ FillMode = FillMode.Fill,
+ Texture = texture,
+ OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
+ };
+
+ public void Add(DrawableHitObject fruit)
+ {
+ float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
+
+ while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
+ {
+ fruit.X += RNG.Next(-5, 5);
+ fruit.Y -= RNG.Next(0, 5);
+ }
+
+ caughtFruit.Add(fruit);
+
+ if (((CatchHitObject)fruit.HitObject).LastInCombo)
+ explode();
+ }
+
+ public bool OnPressed(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection--;
+ return true;
+ case CatchAction.MoveRight:
+ currentDirection++;
+ return true;
+ case CatchAction.Dash:
+ Dashing = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection++;
+ return true;
+ case CatchAction.MoveRight:
+ currentDirection--;
+ return true;
+ case CatchAction.Dash:
+ Dashing = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
+ ///
+ public const double BASE_SPEED = 1.0 / 512;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentDirection == 0) return;
+
+ double dashModifier = Dashing ? 1 : 0.5;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * Math.Sign(currentDirection), Scale.Y);
+ X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
+ }
+
+ private void explode()
+ {
+ var fruit = caughtFruit.ToArray();
+
+ foreach (var f in fruit)
+ {
+ var originalX = f.X * Scale.X;
+
+ if (ExplodingFruitTarget != null)
+ {
+ f.Anchor = Anchor.TopLeft;
+ f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
+
+ caughtFruit.Remove(f);
+
+ ExplodingFruitTarget.Add(f);
+ }
+
+ f.MoveToY(f.Y - 50, 250, Easing.OutSine)
+ .Then()
+ .MoveToY(f.Y + 50, 500, Easing.InSine);
+
+ f.MoveToX(f.X + originalX * 6, 1000);
+ f.FadeOut(750);
+
+ f.Expire();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index a666984b95..b7916f674e 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -57,16 +57,17 @@
-
+
-
+
+
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index f6d30ad3fa..d5a799b4ed 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -138,8 +138,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Pattern newPattern = conversion.Generate();
lastPattern = newPattern;
- var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
- lastStair = stairPatternGenerator.StairType;
+ var stairPatternGenerator = conversion as HitObjectPatternGenerator;
+ lastStair = stairPatternGenerator?.StairType ?? lastStair;
return newPattern.HitObjects;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 67bc347535..e0763284a6 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
{
}
- public override double Calculate(Dictionary categoryDifficulty = null) => 0;
+ public override double Calculate(Dictionary categoryDifficulty = null) => 0;
protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
index 164309c227..dfc9993bde 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
@@ -176,22 +176,10 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModAutoplay : ModAutoplay
{
- private int availableColumns;
-
- public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
- {
- // Todo: This shouldn't be done, we should be getting a ManiaBeatmap which should store AvailableColumns
- // But this is dependent on a _lot_ of refactoring
- var maniaRulesetContainer = (ManiaRulesetContainer)rulesetContainer;
- availableColumns = maniaRulesetContainer.AvailableColumns;
-
- base.ApplyToRulesetContainer(rulesetContainer);
- }
-
protected override Score CreateReplayScore(Beatmap beatmap) => new Score
{
User = new User { Username = "osu!topus!" },
- Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(),
+ Replay = new ManiaAutoGenerator(beatmap).Generate(),
};
}
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 64982532a7..153fee3ab6 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 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.Mania.Objects;
@@ -13,15 +13,11 @@ namespace osu.Game.Rulesets.Mania.Replays
{
internal class ManiaAutoGenerator : AutoGenerator
{
- private const double release_delay = 20;
+ public const double RELEASE_DELAY = 20;
- private readonly int availableColumns;
-
- public ManiaAutoGenerator(Beatmap beatmap, int availableColumns)
+ public ManiaAutoGenerator(Beatmap beatmap)
: base(beatmap)
{
- this.availableColumns = availableColumns;
-
Replay = new Replay { User = new User { Username = @"Autoplay" } };
}
@@ -30,104 +26,52 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate()
{
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
+ Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
- double[] holdEndTimes = new double[availableColumns];
- for (int i = 0; i < availableColumns; i++)
- holdEndTimes[i] = double.NegativeInfinity;
+ var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
- // Notes are handled row-by-row
- foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime))
+ int activeColumns = 0;
+ foreach (var group in pointGroups)
{
- double groupTime = objGroup.Key;
-
- int activeColumns = 0;
-
- // Get the previously held-down active columns
- for (int i = 0; i < availableColumns; i++)
+ foreach (var point in group)
{
- if (holdEndTimes[i] > groupTime)
- activeColumns |= 1 << i;
+ if (point is HitPoint)
+ activeColumns |= 1 << point.Column;
+ if (point is ReleasePoint)
+ activeColumns ^= 1 << point.Column;
}
- // Add on the group columns, keeping track of the held notes for the next rows
- foreach (var obj in objGroup)
- {
- var holdNote = obj as HoldNote;
- if (holdNote != null)
- holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime);
-
- activeColumns |= 1 << obj.Column;
- }
-
- Replay.Frames.Add(new ReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None));
-
- // Add the release frames. We can't do this with the loop above because we need activeColumns to be fully populated
- foreach (var obj in objGroup.GroupBy(h => (h as IHasEndTime)?.EndTime ?? h.StartTime + release_delay).OrderBy(h => h.Key))
- {
- var groupEndTime = obj.Key;
-
- int activeColumnsAtEnd = 0;
- for (int i = 0; i < availableColumns; i++)
- {
- if (holdEndTimes[i] > groupEndTime)
- activeColumnsAtEnd |= 1 << i;
- }
-
- Replay.Frames.Add(new ReplayFrame(groupEndTime, activeColumnsAtEnd, 0, ReplayButtonState.None));
- }
+ Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
}
- Replay.Frames = Replay.Frames
- // Pick the maximum activeColumns for all frames at the same time
- .GroupBy(f => f.Time)
- .Select(g => new ReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None))
- // The addition of release frames above maybe result in unordered frames, but we need them ordered
- .OrderBy(f => f.Time)
- .ToList();
-
return Replay;
}
- ///
- /// Finds the maximum by count of bits from a grouping of s.
- ///
- /// The grouping to search.
- /// The maximum by count of bits.
- private float maxMouseX(IGrouping group)
+ private IEnumerable generateActionPoints()
{
- int currentCount = -1;
- int currentMax = 0;
-
- foreach (var val in group)
+ foreach (var obj in Beatmap.HitObjects)
{
- int newCount = countBits((int)(val.MouseX ?? 0));
- if (newCount > currentCount)
- {
- currentCount = newCount;
- currentMax = (int)(val.MouseX ?? 0);
- }
+ yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
+ yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
}
-
- return currentMax;
}
- ///
- /// Counts the number of bits set in a value.
- ///
- /// The value to count.
- /// The number of set bits.
- private int countBits(int value)
+ private interface IActionPoint
{
- int count = 0;
- while (value > 0)
- {
- if ((value & 1) > 0)
- count++;
- value >>= 1;
- }
+ double Time { get; set; }
+ int Column { get; set; }
+ }
- return count;
+ private struct HitPoint : IActionPoint
+ {
+ public double Time { get; set; }
+ public int Column { get; set; }
+ }
+
+ private struct ReleasePoint : IActionPoint
+ {
+ public double Time { get; set; }
+ public int Column { get; set; }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index e352997f2c..12534d6eb4 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -2,29 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Input;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
{
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
{
- public ManiaFramedReplayInputHandler(Replay replay)
+ private readonly ManiaRulesetContainer container;
+
+ public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
: base(replay)
{
+ this.container = container;
}
+ private ManiaPlayfield playfield;
public override List GetPendingStates()
{
var actions = new List();
- int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
+ if (playfield == null)
+ playfield = (ManiaPlayfield)container.Playfield;
+ int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
int counter = 0;
while (activeColumns > 0)
{
if ((activeColumns & 1) > 0)
- actions.Add(ManiaAction.Key1 + counter);
+ actions.Add(playfield.Columns.ElementAt(counter).Action);
counter++;
activeColumns >>= 1;
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
new file mode 100644
index 0000000000..d1bc7da911
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Mania.Replays
+{
+ public class ManiaReplayFrame : ReplayFrame
+ {
+ public override bool IsImportant => MouseX > 0;
+
+ public ManiaReplayFrame(double time, int activeColumns)
+ : base(time, activeColumns, null, ReplayButtonState.None)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
new file mode 100644
index 0000000000..805553eafc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
@@ -0,0 +1,173 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCaseAutoGeneration : OsuTestCase
+ {
+ [Test]
+ public void TestSingleNote()
+ {
+ // | |
+ // | - |
+ // | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ }
+
+ [Test]
+ public void TestSingleHoldNote()
+ {
+ // | |
+ // | * |
+ // | * |
+ // | * |
+ // | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteChord()
+ {
+ // | | |
+ // | - | - |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteChord()
+ {
+ // | | |
+ // | * | * |
+ // | * | * |
+ // | * | * |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteStair()
+ {
+ // | | |
+ // | | - |
+ // | - | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
+ Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released");
+ Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteStair()
+ {
+ // | | |
+ // | | * |
+ // | * | * |
+ // | * | * |
+ // | * | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
+ Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released");
+ Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteWithReleasePress()
+ {
+ // | | |
+ // | * | - |
+ // | * | |
+ // | * | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
+ beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(2, generated.Frames[2].MouseX, "Key 1 has not been released or key 2 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..8aa8c6b799
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new ManiaRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 08acd46c57..cbbcb84b31 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -124,6 +124,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
+ protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
}
}
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index 6f45a64d92..19832d733e 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -72,6 +72,7 @@
+
@@ -80,8 +81,10 @@
+
+
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 112fcb1a30..39ec753fe1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -45,6 +45,18 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; }
}
+ ///
+ /// The position of the cursor at the point of completion of this if it was hit
+ /// with as few movements as possible. This is set and used by difficulty calculation.
+ ///
+ internal Vector2? LazyEndPosition;
+
+ ///
+ /// The distance travelled by the cursor upon completion of this if it was hit
+ /// with as few movements as possible. This is set and used by difficulty calculation.
+ ///
+ internal float LazyTravelDistance;
+
public List RepeatSamples { get; set; } = new List();
public int RepeatCount { get; set; } = 1;
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
index 537874f643..3d185ab694 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
(h as Slider)?.Curve?.Calculate();
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override double Calculate(Dictionary categoryDifficulty = null)
{
- OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects);
+ OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
Skill[] skills =
{
new Aim(),
@@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
if (categoryDifficulty != null)
{
- categoryDifficulty.Add("Aim", aimRating.ToString("0.00"));
- categoryDifficulty.Add("Speed", speedRating.ToString("0.00"));
+ categoryDifficulty.Add("Aim", aimRating);
+ categoryDifficulty.Add("Speed", speedRating);
}
return starRating;
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
index c6ecc3a506..f8e9423e29 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
/// 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)
+ 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);
+ difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
}
///
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- private IEnumerator createDifficultyObjectEnumerator(List objects)
+ 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];
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
triangle[1] = triangle[0];
triangle[0] = objects[i];
- yield return new OsuDifficultyHitObject(triangle);
+ yield return new OsuDifficultyHitObject(triangle, timeRate);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
index bdeb62df3e..972677a6f1 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Linq;
+using OpenTK;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
@@ -33,13 +35,17 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
private const int normalized_radius = 52;
+ private readonly double timeRate;
+
private readonly OsuHitObject[] t;
///
/// Initializes the object calculating extra data required for difficulty calculation.
///
- public OsuDifficultyHitObject(OsuHitObject[] triangle)
+ public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
{
+ this.timeRate = timeRate;
+
t = triangle;
BaseObject = t[0];
setDistances();
@@ -57,14 +63,53 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
- Distance = (t[0].StackedPosition - t[1].StackedPosition).Length * scalingFactor;
+ Vector2 lastCursorPosition = t[1].StackedPosition;
+ float lastTravelDistance = 0;
+
+ var lastSlider = t[1] as Slider;
+ if (lastSlider != null)
+ {
+ computeSliderCursorPosition(lastSlider);
+ lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
+ lastTravelDistance = lastSlider.LazyTravelDistance;
+ }
+
+ Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
}
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);
+ DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
TimeUntilHit = 450; // BaseObject.PreEmpt;
}
+
+ private void computeSliderCursorPosition(Slider slider)
+ {
+ if (slider.LazyEndPosition != null)
+ return;
+ slider.LazyEndPosition = slider.StackedPosition;
+
+ float approxFollowCircleRadius = (float)(slider.Radius * 3);
+ var computeVertex = new Action(t =>
+ {
+ var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value;
+ float dist = diff.Length;
+
+ if (dist > approxFollowCircleRadius)
+ {
+ // The cursor would be outside the follow circle, we need to move it
+ diff.Normalize(); // Obtain direction of diff
+ dist -= approxFollowCircleRadius;
+ slider.LazyEndPosition += diff * dist;
+ slider.LazyTravelDistance += dist;
+ }
+ });
+
+ var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t);
+ foreach (var time in scoringTimes)
+ computeVertex(time);
+ computeVertex(slider.EndTime);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index c87328d87c..fdf2a458b7 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -14,6 +14,8 @@ 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;
namespace osu.Game.Rulesets.Osu
{
@@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
+ public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
+
public override string Description => "osu!";
public override SettingsSubsection CreateSettings() => new OsuSettings();
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
new file mode 100644
index 0000000000..cd6b6c5e27
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -0,0 +1,199 @@
+// Copyright (c) 2007-2017 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.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
+{
+ public class OsuPerformanceCalculator : PerformanceCalculator
+ {
+ private readonly int countHitCircles;
+ private readonly int beatmapMaxCombo;
+
+ private Mod[] mods;
+ private double realApproachRate;
+ private double accuracy;
+ private int scoreMaxCombo;
+ private int count300;
+ private int count100;
+ private int count50;
+ private int countMiss;
+
+ public OsuPerformanceCalculator(Ruleset ruleset, Beatmap 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.RepeatCount + s.Ticks.Count());
+ }
+
+ public override double Calculate(Dictionary categoryRatings = null)
+ {
+ mods = Score.Mods;
+ accuracy = Score.Accuracy;
+ scoreMaxCombo = Score.MaxCombo;
+ count300 = Convert.ToInt32(Score.Statistics["300"]);
+ count100 = Convert.ToInt32(Score.Statistics["100"]);
+ count50 = Convert.ToInt32(Score.Statistics["50"]);
+ countMiss = Convert.ToInt32(Score.Statistics["x"]);
+
+ // Don't count scores made with supposedly unranked mods
+ if (mods.Any(m => !m.Ranked))
+ return 0;
+
+ // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
+ // locally for now as doing so would modify animations and other things unexpectedly
+ // DO NOT MODIFY THIS
+ double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
+ if (mods.Any(m => m is OsuModHardRock))
+ ar = Math.Min(10, ar * 1.4);
+ if (mods.Any(m => m is OsuModEasy))
+ ar = Math.Max(0, ar / 2);
+ double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
+ realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
+
+ // 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
+
+ if (mods.Any(m => m is OsuModNoFail))
+ multiplier *= 0.90f;
+
+ if (mods.Any(m => m is OsuModSpunOut))
+ multiplier *= 0.95f;
+
+ double aimValue = computeAimValue();
+ double speedValue = computeSpeedValue();
+ double accuracyValue = computeAccuracyValue();
+ double totalValue =
+ Math.Pow(
+ Math.Pow(aimValue, 1.1f) +
+ Math.Pow(speedValue, 1.1f) +
+ Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
+ ) * multiplier;
+
+ if (categoryRatings != null)
+ {
+ categoryRatings.Add("Aim", aimValue);
+ categoryRatings.Add("Speed", speedValue);
+ categoryRatings.Add("Accuracy", accuracyValue);
+ }
+
+ return totalValue;
+ }
+
+ private double computeAimValue()
+ {
+ double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+
+ // Longer maps are worth more
+ double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+
+ aimValue *= lengthBonus;
+
+ // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+ aimValue *= Math.Pow(0.97f, countMiss);
+
+ // Combo scaling
+ if (beatmapMaxCombo > 0)
+ aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+
+ double approachRateFactor = 1.0f;
+ if (realApproachRate > 10.33f)
+ approachRateFactor += 0.45f * (realApproachRate - 10.33f);
+ else if (realApproachRate < 8.0f)
+ {
+ // HD is worth more with lower ar!
+ if (mods.Any(h => h is OsuModHidden))
+ approachRateFactor += 0.02f * (8.0f - realApproachRate);
+ else
+ approachRateFactor += 0.01f * (8.0f - realApproachRate);
+ }
+
+ aimValue *= approachRateFactor;
+
+ if (mods.Any(h => h is OsuModHidden))
+ aimValue *= 1.18f;
+
+ if (mods.Any(h => h is OsuModFlashlight))
+ {
+ // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
+ aimValue *= 1.45f * lengthBonus;
+ }
+
+ // 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;
+
+ return aimValue;
+ }
+
+ private double computeSpeedValue()
+ {
+ double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+
+ // Longer maps are worth more
+ speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+
+ // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+ speedValue *= Math.Pow(0.97f, countMiss);
+
+ // Combo scaling
+ if (beatmapMaxCombo > 0)
+ speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+
+ // 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;
+
+ return speedValue;
+ }
+
+ private double computeAccuracyValue()
+ {
+ // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
+ double betterAccuracyPercentage;
+ int amountHitObjectsWithAccuracy = countHitCircles;
+
+ if (amountHitObjectsWithAccuracy > 0)
+ betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
+ else
+ betterAccuracyPercentage = 0;
+
+ // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
+ if (betterAccuracyPercentage < 0)
+ betterAccuracyPercentage = 0;
+
+ // 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;
+
+ // 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));
+
+ if (mods.Any(m => m is OsuModHidden))
+ accuracyValue *= 1.02f;
+ if (mods.Any(m => m is OsuModFlashlight))
+ accuracyValue *= 1.02f;
+
+ return accuracyValue;
+ }
+
+ private double totalHits => count300 + count100 + count50 + countMiss;
+ private double totalSuccessfulHits => count300 + count100 + count50;
+
+ protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..25a6110459
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new OsuRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 3c90749777..2be057de40 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -85,9 +85,11 @@
+
+
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
index e881942fbf..e74c12fa5d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
@@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using System.Collections.Generic;
-using System.Globalization;
using System;
namespace osu.Game.Rulesets.Taiko
@@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko
{
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override double Calculate(Dictionary categoryDifficulty = null)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
@@ -53,8 +52,8 @@ namespace osu.Game.Rulesets.Taiko
if (categoryDifficulty != null)
{
- categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture));
- categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture));
+ categoryDifficulty.Add("Strain", starRating);
+ categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
}
return starRating;
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..96d5b20b6e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new TaikoRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index bf627d205a..0b4e6e43f2 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -83,6 +83,7 @@
+
diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
new file mode 100644
index 0000000000..d260de69f1
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
@@ -0,0 +1,33 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Screens.Menu;
+using OpenTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ internal class TestCaseButtonSystem : OsuTestCase
+ {
+ public TestCaseButtonSystem()
+ {
+ OsuLogo logo;
+ ButtonSystem buttons;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke),
+ RelativeSizeAxes = Axes.Both,
+ },
+ buttons = new ButtonSystem(),
+ logo = new OsuLogo()
+ };
+
+ buttons.SetOsuLogo(logo);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMenuButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseMenuButtonSystem.cs
deleted file mode 100644
index b5310f0fb0..0000000000
--- a/osu.Game.Tests/Visual/TestCaseMenuButtonSystem.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System.ComponentModel;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Screens.Menu;
-using OpenTK.Graphics;
-
-namespace osu.Game.Tests.Visual
-{
- [Description("main menu")]
- internal class TestCaseMenuButtonSystem : OsuTestCase
- {
- public TestCaseMenuButtonSystem()
- {
- Add(new Box
- {
- Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke),
- RelativeSizeAxes = Framework.Graphics.Axes.Both,
- });
- Add(new ButtonSystem());
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
index 22a2d717e4..badb98e6b7 100644
--- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual
Add(container = new ExampleContainer());
- AddStep(@"Add button", () => container.Add(new OsuButton
+ AddStep(@"Add button", () => container.Add(new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = @"Button",
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 9bba09b1a7..312a564f71 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -116,7 +116,7 @@
-
+
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 962c790fb2..e087eebbfe 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -80,6 +80,7 @@ namespace osu.Game.Beatmaps
///
/// Performs the conversion of a hit object.
+ /// This method is generally executed sequentially for all objects in a beatmap.
///
/// The hit object to convert.
/// The un-converted Beatmap.
diff --git a/osu.Game/Beatmaps/BeatmapMetrics.cs b/osu.Game/Beatmaps/BeatmapMetrics.cs
index 730cf635da..e0cd5f10e7 100644
--- a/osu.Game/Beatmaps/BeatmapMetrics.cs
+++ b/osu.Game/Beatmaps/BeatmapMetrics.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
public class BeatmapMetrics
{
///
- /// Total vote counts of user ratings on a scale of 0..length.
+ /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?).
///
public IEnumerable Ratings { get; set; }
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index f58f433cb2..687e1b2177 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
{
protected double TimeRate = 1;
- public abstract double Calculate(Dictionary categoryDifficulty = null);
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
}
public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject
diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs
index d6ed306b39..c990a0ea46 100644
--- a/osu.Game/Beatmaps/Drawables/Panel.cs
+++ b/osu.Game/Beatmaps/Drawables/Panel.cs
@@ -3,12 +3,18 @@
using System;
using osu.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.MathUtils;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
@@ -22,6 +28,10 @@ namespace osu.Game.Beatmaps.Drawables
private readonly Container nestedContainer;
+ private readonly Container borderContainer;
+
+ private readonly Box hoverLayer;
+
protected override Container Content => nestedContainer;
protected Panel()
@@ -29,20 +39,56 @@ namespace osu.Game.Beatmaps.Drawables
Height = MAX_HEIGHT;
RelativeSizeAxes = Axes.X;
- AddInternal(nestedContainer = new Container
+ AddInternal(borderContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
BorderColour = new Color4(221, 255, 255, 255),
+ Children = new Drawable[]
+ {
+ nestedContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ hoverLayer = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ Blending = BlendingMode.Additive,
+ },
+ }
});
Alpha = 0;
}
+ private SampleChannel sampleHover;
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, OsuColour colours)
+ {
+ sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
+ hoverLayer.Colour = colours.Blue.Opacity(0.1f);
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ sampleHover?.Play();
+
+ hoverLayer.FadeIn(100, Easing.OutQuint);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ hoverLayer.FadeOut(1000, Easing.OutQuint);
+ base.OnHoverLost(state);
+ }
+
public void SetMultiplicativeAlpha(float alpha)
{
- nestedContainer.Alpha = alpha;
+ borderContainer.Alpha = alpha;
}
protected override void LoadComplete()
@@ -94,8 +140,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Selected()
{
- nestedContainer.BorderThickness = 2.5f;
- nestedContainer.EdgeEffect = new EdgeEffectParameters
+ borderContainer.BorderThickness = 2.5f;
+ borderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150),
@@ -106,8 +152,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Deselected()
{
- nestedContainer.BorderThickness = 0;
- nestedContainer.EdgeEffect = new EdgeEffectParameters
+ borderContainer.BorderThickness = 0;
+ borderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(1),
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index b3e99fce06..1a7d29e907 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -16,12 +16,13 @@ namespace osu.Game.Configuration
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
+ Set(OsuSetting.ShowConvertedBeatmaps, true);
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation);
- Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1, 0.01);
+ Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
// Online settings
Set(OsuSetting.Username, string.Empty);
@@ -112,6 +113,7 @@ namespace osu.Game.Configuration
SnakingOutSliders,
ShowFpsDisplay,
ChatDisplayHeight,
- Version
+ Version,
+ ShowConvertedBeatmaps
}
}
diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs
index bc1b7132eb..d8c3ce6694 100644
--- a/osu.Game/Database/DatabaseBackedStore.cs
+++ b/osu.Game/Database/DatabaseBackedStore.cs
@@ -35,6 +35,7 @@ namespace osu.Game.Database
var id = obj.ID;
obj = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id);
+ context.Entry(obj).Reload();
}
///
diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
index 11c049ed3e..8df533ad6e 100644
--- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
@@ -2,34 +2,39 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers
{
public class OsuClickableContainer : ClickableContainer
{
- protected SampleChannel SampleClick, SampleHover;
+ private readonly HoverSampleSet sampleSet;
+
+ private readonly Container content = new Container { RelativeSizeAxes = Axes.Both };
+
+ protected override Container Content => content;
+
+ public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal)
+ {
+ this.sampleSet = sampleSet;
+ }
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load()
{
- SampleHover = audio.Sample.Get(@"UI/generic-hover");
- SampleClick = audio.Sample.Get(@"UI/generic-click");
- }
+ if (AutoSizeAxes != Axes.None)
+ {
+ content.RelativeSizeAxes = RelativeSizeAxes;
+ content.AutoSizeAxes = AutoSizeAxes;
+ }
- protected override bool OnHover(InputState state)
- {
- SampleHover?.Play();
- return base.OnHover(state);
- }
-
- protected override bool OnClick(InputState state)
- {
- SampleClick?.Play();
- return base.OnClick(state);
+ InternalChildren = new Drawable[]
+ {
+ content,
+ new HoverClickSounds(sampleSet)
+ };
}
}
}
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 4ea4f4cdc3..c788df3066 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -16,8 +16,8 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
- samplePopIn = audio.Sample.Get(@"UI/melodic-5");
- samplePopOut = audio.Sample.Get(@"UI/melodic-4");
+ samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
+ samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
StateChanged += onStateChanged;
}
diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs
index ca108bfa7a..e752b1d91a 100644
--- a/osu.Game/Graphics/SpriteIcon.cs
+++ b/osu.Game/Graphics/SpriteIcon.cs
@@ -57,19 +57,31 @@ namespace osu.Game.Graphics
private void load(FontStore store)
{
this.store = store;
-
updateTexture();
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateTexture();
+ }
+
+ private FontAwesome loadedIcon;
private void updateTexture()
{
- var texture = store?.Get(((char)icon).ToString());
+ var loadableIcon = icon;
+
+ if (loadableIcon == loadedIcon) return;
+
+ var texture = store?.Get(((char)loadableIcon).ToString());
spriteMain.Texture = texture;
spriteShadow.Texture = texture;
if (Size == Vector2.Zero)
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
+
+ loadedIcon = loadableIcon;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs
index 20df553142..c25a9bf5e9 100644
--- a/osu.Game/Graphics/UserInterface/Bar.cs
+++ b/osu.Game/Graphics/UserInterface/Bar.cs
@@ -20,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface
private const Easing easing = Easing.InOutCubic;
private float length;
+
///
/// Length of the bar, ranges from 0 to 1
///
@@ -134,4 +135,4 @@ namespace osu.Game.Graphics.UserInterface
Vertical = TopToBottom | BottomToTop,
Horizontal = LeftToRight | RightToLeft,
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
new file mode 100644
index 0000000000..0fac1c8092
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
@@ -0,0 +1,36 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Extensions;
+using osu.Framework.Input;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ ///
+ /// Adds hover and click sounds to a drawable.
+ /// Does not draw anything.
+ ///
+ public class HoverClickSounds : HoverSounds
+ {
+ private SampleChannel sampleClick;
+
+ public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet)
+ {
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ sampleClick?.Play();
+ return base.OnClick(state);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}");
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs
new file mode 100644
index 0000000000..24dbe37567
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.ComponentModel;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ ///
+ /// Adds hover sounds to a drawable.
+ /// Does not draw anything.
+ ///
+ public class HoverSounds : CompositeDrawable
+ {
+ private SampleChannel sampleHover;
+
+ protected readonly HoverSampleSet SampleSet;
+
+ public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
+ {
+ SampleSet = sampleSet;
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ sampleHover?.Play();
+ return base.OnHover(state);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
+ }
+ }
+
+ public enum HoverSampleSet
+ {
+ [Description("")]
+ Loud,
+ [Description("-soft")]
+ Normal,
+ [Description("-softer")]
+ Soft
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs
index ccc23e3ff6..081e59d3a7 100644
--- a/osu.Game/Graphics/UserInterface/OsuButton.cs
+++ b/osu.Game/Graphics/UserInterface/OsuButton.cs
@@ -1,126 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System.Collections.Generic;
-using OpenTK.Graphics;
-using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
- public class OsuButton : Button, IFilterable
+ ///
+ /// A button with added default sound effects.
+ ///
+ public class OsuButton : Button
{
- private Box hover;
-
- private SampleChannel sampleClick;
- private SampleChannel sampleHover;
-
- protected Triangles Triangles;
-
public OsuButton()
{
- Height = 40;
- }
-
- protected override SpriteText CreateText() => new OsuSpriteText
- {
- Depth = -1,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Font = @"Exo2.0-Bold",
- };
-
- public override bool HandleInput => Action != null;
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours, AudioManager audio)
- {
- if (Action == null)
- Colour = OsuColour.Gray(0.5f);
-
- BackgroundColour = colours.BlueDark;
-
- Content.Masking = true;
- Content.CornerRadius = 5;
-
- AddRange(new Drawable[]
- {
- Triangles = new Triangles
- {
- RelativeSizeAxes = Axes.Both,
- ColourDark = colours.BlueDarker,
- ColourLight = colours.Blue,
- },
- hover = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Colour = Color4.White.Opacity(0.1f),
- Alpha = 0,
- },
- });
-
- sampleClick = audio.Sample.Get(@"UI/generic-click");
- sampleHover = audio.Sample.Get(@"UI/generic-hover");
-
- Enabled.ValueChanged += enabled_ValueChanged;
- Enabled.TriggerChange();
- }
-
- private void enabled_ValueChanged(bool enabled)
- {
- this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
- }
-
- protected override bool OnClick(InputState state)
- {
- sampleClick?.Play();
- return base.OnClick(state);
- }
-
- protected override bool OnHover(InputState state)
- {
- sampleHover?.Play();
- hover.FadeIn(200);
- return base.OnHover(state);
- }
-
- protected override void OnHoverLost(InputState state)
- {
- hover.FadeOut(200);
- base.OnHoverLost(state);
- }
-
- protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
- {
- Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
- return base.OnMouseDown(state, args);
- }
-
- protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
- {
- Content.ScaleTo(1, 1000, Easing.OutElastic);
- return base.OnMouseUp(state, args);
- }
-
- public IEnumerable FilterTerms => new[] { Text };
-
- public bool MatchingFilter
- {
- set
- {
- this.FadeTo(value ? 1 : 0);
- }
+ Add(new HoverClickSounds(HoverSampleSet.Loud));
}
}
}
diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs
index 68ff99e593..40ff1243dc 100644
--- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs
@@ -70,7 +70,8 @@ namespace osu.Game.Graphics.UserInterface
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 5 },
- }
+ },
+ new HoverClickSounds()
};
Nub.Current.BindTo(Current);
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index f605804aaa..4401b509fd 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -33,7 +33,6 @@ namespace osu.Game.Graphics.UserInterface
if (accentColour == default(Color4))
accentColour = colours.PinkDarker;
updateAccentColour();
-
}
private void updateAccentColour()
@@ -137,6 +136,8 @@ namespace osu.Game.Graphics.UserInterface
nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours();
+
+ AddInternal(new HoverClickSounds(HoverSampleSet.Soft));
}
protected override void UpdateForegroundColour()
@@ -183,7 +184,7 @@ namespace osu.Game.Graphics.UserInterface
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
- }
+ },
};
}
}
@@ -237,8 +238,10 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 4 },
Size = new Vector2(20),
- }
+ },
};
+
+ AddInternal(new HoverClickSounds());
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs
index 3fd5481152..7f16d73613 100644
--- a/osu.Game/Graphics/UserInterface/OsuMenu.cs
+++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Graphics.UserInterface
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
- sampleClick = audio.Sample.Get(@"UI/generic-click");
+ sampleClick = audio.Sample.Get(@"UI/generic-select");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index 3dd3596c30..fd75269610 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -88,7 +88,8 @@ namespace osu.Game.Graphics.UserInterface
{
Origin = Anchor.TopCentre,
Expanded = true,
- }
+ },
+ new HoverClickSounds()
};
Current.DisabledChanged += disabled =>
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index b053195030..decbf57ad1 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -131,7 +131,8 @@ namespace osu.Game.Graphics.UserInterface
Colour = Color4.White,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
- }
+ },
+ new HoverClickSounds()
};
}
diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs
index 6b97e54ecd..c69857a5c4 100644
--- a/osu.Game/Graphics/UserInterface/PageTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs
@@ -57,6 +57,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
},
+ new HoverClickSounds()
};
}
diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs
new file mode 100644
index 0000000000..61e9705064
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs
@@ -0,0 +1,108 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ ///
+ /// A button with moving triangles in the background.
+ ///
+ public class TriangleButton : OsuButton, IFilterable
+ {
+ private Box hover;
+
+ protected Triangles Triangles;
+
+ public TriangleButton()
+ {
+ Height = 40;
+ }
+
+ protected override SpriteText CreateText() => new OsuSpriteText
+ {
+ Depth = -1,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Font = @"Exo2.0-Bold",
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ BackgroundColour = colours.BlueDark;
+
+ Content.Masking = true;
+ Content.CornerRadius = 5;
+
+ AddRange(new Drawable[]
+ {
+ Triangles = new Triangles
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColourDark = colours.BlueDarker,
+ ColourLight = colours.Blue,
+ },
+ hover = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingMode.Additive,
+ Colour = Color4.White.Opacity(0.1f),
+ Alpha = 0,
+ },
+ });
+
+ Enabled.ValueChanged += enabled_ValueChanged;
+ Enabled.TriggerChange();
+ }
+
+ private void enabled_ValueChanged(bool enabled)
+ {
+ this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ hover.FadeIn(200);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ hover.FadeOut(200);
+ base.OnHoverLost(state);
+ }
+
+ protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+ {
+ Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
+ return base.OnMouseDown(state, args);
+ }
+
+ protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+ {
+ Content.ScaleTo(1, 1000, Easing.OutElastic);
+ return base.OnMouseUp(state, args);
+ }
+
+ public IEnumerable FilterTerms => new[] { Text };
+
+ public bool MatchingFilter
+ {
+ set
+ {
+ this.FadeTo(value ? 1 : 0);
+ }
+ }
+ }
+}
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 50639e3427..8eaa20f781 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Reflection;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Configuration;
using osu.Framework.Development;
using osu.Framework.Graphics;
@@ -137,6 +138,10 @@ namespace osu.Game
Beatmap = new NonNullableBindable(defaultBeatmap);
BeatmapManager.DefaultBeatmap = defaultBeatmap;
+ // tracks play so loud our samples can't keep up.
+ // this adds a global reduction of track volume for the time being.
+ Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8));
+
Beatmap.ValueChanged += b =>
{
var trackLoaded = lastBeatmap?.TrackLoaded ?? false;
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index af59b21713..4135aef268 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -234,7 +234,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void handleBeatmapAdd(BeatmapSetInfo beatmap)
{
- if (beatmap.OnlineBeatmapSetID == BeatmapSet.OnlineBeatmapSetID)
+ if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
downloadButtonsContainer.FadeOut(transition_duration);
}
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 4db6bdf5e4..32f933ff42 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Chat
}
- private class MessageSender : ClickableContainer, IHasContextMenu
+ private class MessageSender : OsuClickableContainer, IHasContextMenu
{
private readonly User sender;
diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs
index 9f1028c168..f58ee8f819 100644
--- a/osu.Game/Overlays/Chat/ChatTabControl.cs
+++ b/osu.Game/Overlays/Chat/ChatTabControl.cs
@@ -17,6 +17,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using System;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
{
@@ -259,7 +260,7 @@ namespace osu.Game.Overlays.Chat
};
}
- public class CloseButton : ClickableContainer
+ public class CloseButton : OsuClickableContainer
{
private readonly SpriteIcon icon;
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 6f7fabb910..0b7a30797d 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Overlays
{
if (beatmapSets?.Equals(value) ?? false) return;
- beatmapSets = value;
+ beatmapSets = value?.ToList();
if (beatmapSets == null) return;
@@ -65,8 +65,6 @@ namespace osu.Game.Overlays
}
ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags));
-
- recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
}
}
@@ -222,7 +220,11 @@ namespace osu.Game.Overlays
switch (displayStyle)
{
case PanelDisplayStyle.Grid:
- return new DirectGridPanel(b);
+ return new DirectGridPanel(b)
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
default:
return new DirectListPanel(b);
}
@@ -282,7 +284,11 @@ namespace osu.Game.Overlays
var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList();
// may not need scheduling; loads async internally.
- Schedule(() => BeatmapSets = sets);
+ Schedule(() =>
+ {
+ BeatmapSets = sets;
+ recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
+ });
});
};
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
index 30ff0ab026..c670cc0153 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays.KeyBinding
}
}
- public class ResetButton : OsuButton
+ public class ResetButton : TriangleButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 0ead4ea019..77b7c3add2 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
using System;
using System.Linq;
using osu.Framework.Graphics.Cursor;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Mods
{
@@ -148,7 +149,7 @@ namespace osu.Game.Overlays.Mods
// the mods from Mod, only multiple if Mod is a MultiMod
- public override Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
+ public virtual Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
[BackgroundDependencyLoader]
private void load(AudioManager audio)
@@ -253,6 +254,7 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre,
TextSize = 18,
},
+ new HoverClickSounds()
};
Mod = mod;
diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs
index 638c2a0e47..f776c174d2 100644
--- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs
+++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs
@@ -3,7 +3,6 @@
using OpenTK;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods
{
@@ -12,8 +11,6 @@ namespace osu.Game.Overlays.Mods
///
public class ModButtonEmpty : Container
{
- public virtual Mod SelectedMod => null;
-
public ModButtonEmpty()
{
Size = new Vector2(100f);
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
index 07a8e7464a..9875ee8004 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
@@ -17,6 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Children = new Drawable[]
{
+ new SettingsCheckbox
+ {
+ LabelText = "Show converted beatmaps",
+ Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps),
+ },
new SettingsSlider
{
LabelText = "Display beatmaps from",
@@ -33,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
LabelText = "Random beatmap selection",
Bindable = config.GetBindable(OsuSetting.SelectionRandomType),
- },
+ }
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index 4c82a9ae4b..4f4f381ae1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -12,9 +12,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
public class GeneralSettings : SettingsSubsection
{
- private OsuButton importButton;
- private OsuButton deleteButton;
- private OsuButton restoreButton;
+ private TriangleButton importButton;
+ private TriangleButton deleteButton;
+ private TriangleButton restoreButton;
protected override string Header => "General";
diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs
index 5320cef850..19493f6c70 100644
--- a/osu.Game/Overlays/Settings/SettingsButton.cs
+++ b/osu.Game/Overlays/Settings/SettingsButton.cs
@@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
- public class SettingsButton : OsuButton
+ public class SettingsButton : TriangleButton
{
public SettingsButton()
{
diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs
index b39c8ab7cf..4b8366f0fc 100644
--- a/osu.Game/Overlays/Settings/SidebarButton.cs
+++ b/osu.Game/Overlays/Settings/SidebarButton.cs
@@ -12,14 +12,14 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
- public class SidebarButton : Container
+ public class SidebarButton : OsuButton
{
private readonly SpriteIcon drawableIcon;
private readonly SpriteText headerText;
- private readonly Box backgroundBox;
private readonly Box selectionIndicator;
private readonly Container text;
public Action Action;
@@ -61,17 +61,14 @@ namespace osu.Game.Overlays.Settings
public SidebarButton()
{
+ BackgroundColour = OsuColour.Gray(60);
+ Background.Alpha = 0;
+
Height = Sidebar.DEFAULT_WIDTH;
RelativeSizeAxes = Axes.X;
- Children = new Drawable[]
+
+ AddRange(new Drawable[]
{
- backgroundBox = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Colour = OsuColour.Gray(60),
- Alpha = 0,
- },
text = new Container
{
Width = Sidebar.DEFAULT_WIDTH,
@@ -101,7 +98,7 @@ namespace osu.Game.Overlays.Settings
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
}
- };
+ });
}
[BackgroundDependencyLoader]
@@ -113,20 +110,19 @@ namespace osu.Game.Overlays.Settings
protected override bool OnClick(InputState state)
{
Action?.Invoke(section);
- backgroundBox.FlashColour(Color4.White, 400);
- return true;
+ return base.OnClick(state);
}
protected override bool OnHover(InputState state)
{
- backgroundBox.FadeTo(0.4f, 200);
+ Background.FadeTo(0.4f, 200);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
- backgroundBox.FadeTo(0, 200);
+ Background.FadeTo(0, 200);
base.OnHoverLost(state);
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index cb17216679..c039f9d311 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -13,6 +13,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Toolbar
{
@@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar
private readonly SpriteText tooltip2;
protected FillFlowContainer Flow;
- public ToolbarButton()
+ public ToolbarButton() : base(HoverSampleSet.Loud)
{
Width = WIDTH;
RelativeSizeAxes = Axes.Y;
@@ -195,4 +196,4 @@ namespace osu.Game.Overlays.Toolbar
};
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs
index b0f62e5271..02c969f648 100644
--- a/osu.Game/Rulesets/Replays/ReplayFrame.cs
+++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Replays
{
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
- public bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
+ public virtual bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
public float? MouseX;
public float? MouseY;
@@ -68,4 +68,4 @@ namespace osu.Game.Rulesets.Replays
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index ed2fdf4157..d787da6a0a 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets
@@ -49,6 +50,8 @@ namespace osu.Game.Rulesets
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
+ public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
+
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
public abstract string Description { get; }
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
new file mode 100644
index 0000000000..4f603049db
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2007-2017 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;
+
+namespace osu.Game.Rulesets.Scoring
+{
+ public abstract class PerformanceCalculator
+ {
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
+ }
+
+ public abstract class PerformanceCalculator : PerformanceCalculator
+ where TObject : HitObject
+ {
+ private readonly Dictionary attributes = new Dictionary();
+ protected IDictionary Attributes => attributes;
+
+ protected readonly Beatmap Beatmap;
+ protected readonly Score Score;
+
+ protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+ {
+ Beatmap = CreateBeatmapConverter().Convert(beatmap);
+ Score = score;
+
+ var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
+ diffCalc.Calculate(attributes);
+ }
+
+ protected abstract BeatmapConverter CreateBeatmapConverter();
+ }
+}
diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs
index ccd61643ce..5e55166c19 100644
--- a/osu.Game/Screens/Menu/Button.cs
+++ b/osu.Game/Screens/Menu/Button.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
- sampleHover = audio.Sample.Get(@"Menu/hover");
+ sampleHover = audio.Sample.Get(@"Menu/button-hover");
if (!string.IsNullOrEmpty(sampleName))
sampleClick = audio.Sample.Get($@"Menu/{sampleName}");
}
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 5a4a5f07b5..ce7856c5a9 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -117,13 +117,13 @@ namespace osu.Game.Screens.Menu
},
};
- buttonsPlay.Add(new Button(@"solo", @"select-6", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
- buttonsPlay.Add(new Button(@"multi", @"select-5", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M));
- buttonsPlay.Add(new Button(@"chart", @"select-5", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
+ buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
+ buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M));
+ buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
- buttonsTopLevel.Add(new Button(@"play", @"select-1", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P));
- buttonsTopLevel.Add(new Button(@"osu!editor", @"select-5", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
- buttonsTopLevel.Add(new Button(@"osu!direct", string.Empty, FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
+ buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P));
+ buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
+ buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), onExit, 0, Key.Q));
buttonFlow.AddRange(buttonsPlay);
@@ -134,7 +134,7 @@ namespace osu.Game.Screens.Menu
private void load(AudioManager audio, OsuGame game = null)
{
toolbar = game?.Toolbar;
- sampleBack = audio.Sample.Get(@"Menu/select-4");
+ sampleBack = audio.Sample.Get(@"Menu/button-back-select");
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@@ -180,19 +180,21 @@ namespace osu.Game.Screens.Menu
State = MenuState.TopLevel;
}
- private void onOsuLogo()
+ private bool onOsuLogo()
{
switch (state)
{
+ default:
+ return true;
case MenuState.Initial:
State = MenuState.TopLevel;
- return;
+ return true;
case MenuState.TopLevel:
buttonsTopLevel.First().TriggerOnClick();
- return;
+ return false;
case MenuState.Play:
buttonsPlay.First().TriggerOnClick();
- return;
+ return false;
}
}
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 252f2d37b5..9ca12702e5 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -48,7 +48,10 @@ namespace osu.Game.Screens.Menu
private readonly Triangles triangles;
- public Action Action;
+ ///
+ /// Return value decides whether the logo should play its own sample for the click action.
+ ///
+ public Func Action;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@@ -248,8 +251,8 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(TextureStore textures, AudioManager audio)
{
- sampleClick = audio.Sample.Get(@"Menu/select-2");
- sampleBeat = audio.Sample.Get(@"Menu/heartbeat");
+ sampleClick = audio.Sample.Get(@"Menu/osu-logo-select");
+ sampleBeat = audio.Sample.Get(@"Menu/osu-logo-heartbeat");
logo.Texture = textures.Get(@"Menu/logo");
ripple.Texture = textures.Get(@"Menu/logo");
@@ -354,13 +357,12 @@ namespace osu.Game.Screens.Menu
{
if (!interactive) return false;
- sampleClick.Play();
+ if (Action?.Invoke() ?? true)
+ sampleClick.Play();
flashLayer.ClearTransforms();
flashLayer.Alpha = 0.4f;
flashLayer.FadeOut(1500, Easing.OutExpo);
-
- Action?.Invoke();
return true;
}
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index f5ff9ea036..76ee4a607e 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Screens
if (osuGame != null)
Ruleset.BindTo(osuGame.Ruleset);
- sampleExit = audio.Sample.Get(@"UI/melodic-1");
+ sampleExit = audio.Sample.Get(@"UI/screen-back");
}
protected override void OnResuming(Screen last)
diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs
index eed5cd1c20..5f5eeb63a0 100644
--- a/osu.Game/Screens/Play/PauseContainer.cs
+++ b/osu.Game/Screens/Play/PauseContainer.cs
@@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play
{
public bool IsPaused { get; private set; }
- public bool AllowExit => IsPaused && pauseOverlay.Alpha == 1;
-
public Func CheckCanPause;
private const double pause_cooldown = 1000;
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 1e1b7bac93..dc746b305c 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -46,6 +46,8 @@ namespace osu.Game.Screens.Play
public bool HasFailed { get; private set; }
+ public bool AllowPause { get; set; } = true;
+
public int RestartCount;
private IAdjustableClock adjustableSourceClock;
@@ -158,7 +160,7 @@ namespace osu.Game.Screens.Play
FramedClock = offsetClock,
OnRetry = Restart,
OnQuit = Exit,
- CheckCanPause = () => ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
+ CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
Retries = RestartCount,
OnPause = () => {
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
@@ -308,7 +310,7 @@ namespace osu.Game.Screens.Play
if (!loadedSuccessfully)
return;
- (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, Easing.OutQuint);
+ (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1000, Easing.OutQuint);
dimLevel.ValueChanged += dimLevel_ValueChanged;
showStoryboard.ValueChanged += showStoryboard_ValueChanged;
@@ -355,7 +357,7 @@ namespace osu.Game.Screens.Play
protected override bool OnExiting(Screen next)
{
- if (HasFailed || !ValidForResume || pauseContainer?.AllowExit != false || RulesetContainer?.HasReplayLoaded != false)
+ if (!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused != false || RulesetContainer?.HasReplayLoaded != false)
{
// In the case of replays, we may have changed the playback rate.
applyRateFromMods();
diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs
index 2153eb150c..19bcad367e 100644
--- a/osu.Game/Screens/Select/Details/UserRatings.cs
+++ b/osu.Game/Screens/Select/Details/UserRatings.cs
@@ -29,11 +29,17 @@ namespace osu.Game.Screens.Select.Details
if (value == metrics) return;
metrics = value;
- var ratings = Metrics.Ratings.ToList();
- negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2 + 1).Sum().ToString();
- positiveRatings.Text = ratings.GetRange(ratings.Count / 2 + 1, ratings.Count / 2).Sum().ToString();
- ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2 + 1).Sum() / ratings.Sum();
- graph.Values = Metrics.Ratings.Select(r => (float)r);
+ const int rating_range = 10;
+
+ var ratings = Metrics.Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0.
+
+ var negativeCount = ratings.Take(rating_range / 2).Sum();
+ var totalCount = ratings.Sum();
+
+ negativeRatings.Text = negativeCount.ToString();
+ positiveRatings.Text = (totalCount - negativeCount).ToString();
+ ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount;
+ graph.Values = ratings.Take(rating_range).Select(r => (float)r);
}
}
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index e83613125b..1b86cec613 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -15,6 +15,7 @@ using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Input;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Configuration;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Select
@@ -60,6 +61,7 @@ namespace osu.Game.Screens.Select
Group = group,
Sort = sort,
SearchText = searchTextBox.Text,
+ AllowConvertedBeatmaps = showConverted,
Ruleset = ruleset
};
@@ -163,17 +165,24 @@ namespace osu.Game.Screens.Select
private readonly Bindable ruleset = new Bindable();
+ private Bindable showConverted;
+
[BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuColour colours, OsuGame osu)
+ private void load(OsuColour colours, OsuGame osu, OsuConfigManager config)
{
sortTabs.AccentColour = colours.GreenLight;
+ showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps);
+ showConverted.ValueChanged += val => updateCriteria();
+
if (osu != null)
ruleset.BindTo(osu.Ruleset);
- ruleset.ValueChanged += val => FilterChanged?.Invoke(CreateCriteria());
+ ruleset.ValueChanged += val => updateCriteria();
ruleset.TriggerChange();
}
+ private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
+
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseMove(InputState state) => true;
@@ -182,4 +191,4 @@ namespace osu.Game.Screens.Select
protected override bool OnDragStart(InputState state) => true;
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs
index 6c1fb1703d..c1355bfa63 100644
--- a/osu.Game/Screens/Select/FilterCriteria.cs
+++ b/osu.Game/Screens/Select/FilterCriteria.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Screens.Select
public SortMode Sort;
public string SearchText;
public RulesetInfo Ruleset;
+ public bool AllowConvertedBeatmaps;
public void Filter(List groups)
{
@@ -23,7 +24,7 @@ namespace osu.Game.Screens.Select
{
var set = g.BeatmapSet;
- bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0));
+ bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0));
bool match = hasCurrentMode;
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index e0a3693371..bba6ddf577 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -4,6 +4,8 @@
using System.Linq;
using OpenTK.Input;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@@ -42,9 +44,13 @@ namespace osu.Game.Screens.Select
beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
}
+ private SampleChannel sampleConfirm;
+
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, AudioManager audio)
{
+ sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
+
Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
@@ -128,6 +134,8 @@ namespace osu.Game.Screens.Select
Beatmap.Value.Track.Looping = false;
Beatmap.Disabled = true;
+ sampleConfirm?.Play();
+
LoadComponentAsync(player = new PlayerLoader(new Player()), l => Push(player));
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 5500d06136..a0b788d777 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -203,8 +203,8 @@ namespace osu.Game.Screens.Select
Push(new Editor());
}
- private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b);
- private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b);
+ private void onBeatmapRestored(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
+ private void onBeatmapHidden(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
private void carouselBeatmapsLoaded()
{
@@ -332,7 +332,11 @@ namespace osu.Game.Screens.Select
logo.FadeIn(logo_transition, Easing.OutQuint);
logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint);
- logo.Action = () => carouselRaisedStart();
+ logo.Action = () =>
+ {
+ carouselRaisedStart();
+ return false;
+ };
}
protected override void LogoExiting(OsuLogo logo)
@@ -413,7 +417,7 @@ namespace osu.Game.Screens.Select
if (backgroundModeBeatmap != null)
{
backgroundModeBeatmap.Beatmap = beatmap;
- backgroundModeBeatmap.BlurTo(background_blur, 1000);
+ backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint);
backgroundModeBeatmap.FadeTo(1, 250);
}
diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs
index e540782fc1..3e7ab56c99 100644
--- a/osu.Game/Screens/Tournament/Drawings.cs
+++ b/osu.Game/Screens/Tournament/Drawings.cs
@@ -193,21 +193,21 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[]
{
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = "Begin random",
Action = teamsContainer.StartScrolling,
},
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = "Stop random",
Action = teamsContainer.StopScrolling,
},
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
@@ -232,7 +232,7 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[]
{
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs
new file mode 100644
index 0000000000..2f0831d84a
--- /dev/null
+++ b/osu.Game/Tests/Visual/ScreenTestCase.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Screens;
+using osu.Game.Screens;
+
+namespace osu.Game.Tests.Visual
+{
+ ///
+ /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
+ ///
+ public abstract class ScreenTestCase : OsuTestCase
+ {
+ private readonly TestOsuScreen baseScreen;
+
+ protected ScreenTestCase()
+ {
+ Add(baseScreen = new TestOsuScreen());
+ }
+
+ protected void LoadScreen(OsuScreen screen) => baseScreen.LoadScreen(screen);
+
+ public class TestOsuScreen : OsuScreen
+ {
+ private OsuScreen nextScreen;
+
+ public void LoadScreen(OsuScreen screen) => Schedule(() =>
+ {
+ nextScreen = screen;
+
+ if (IsCurrentScreen)
+ {
+ Push(screen);
+ nextScreen = null;
+ }
+ else
+ MakeCurrent();
+ });
+
+ protected override void OnResuming(Screen last)
+ {
+ base.OnResuming(last);
+ if (nextScreen != null)
+ LoadScreen(nextScreen);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..6da14e9b12
--- /dev/null
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -0,0 +1,395 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Caching;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Tests.Visual
+{
+ public abstract class TestCasePerformancePoints : OsuTestCase
+ {
+ protected TestCasePerformancePoints(Ruleset ruleset)
+ {
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new BeatmapList(ruleset)
+ }
+ }
+ },
+ null,
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new StarRatingGrid()
+ }
+ }
+ },
+ null,
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new PerformanceList()
+ }
+ }
+ },
+ }
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 20),
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 20)
+ }
+ };
+ }
+
+ private class BeatmapList : CompositeDrawable
+ {
+ private readonly Container beatmapDisplays;
+ private readonly Ruleset ruleset;
+
+ public BeatmapList(Ruleset ruleset)
+ {
+ this.ruleset = ruleset;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = beatmapDisplays = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4)
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ var sets = beatmaps.GetAllUsableBeatmapSets();
+ var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID < 0 || b.RulesetID == ruleset.LegacyID);
+
+ allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b)));
+ }
+
+ private class BeatmapDisplay : CompositeDrawable, IHasTooltip
+ {
+ private readonly OsuSpriteText text;
+ private readonly BeatmapInfo beatmap;
+
+ private BeatmapManager beatmaps;
+ private OsuGameBase osuGame;
+
+ private bool isSelected;
+
+ public string TooltipText => text.Text;
+
+ public BeatmapDisplay(BeatmapInfo beatmap)
+ {
+ this.beatmap = beatmap;
+
+ AutoSizeAxes = Axes.Both;
+ InternalChild = text = new OsuSpriteText();
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if (osuGame.Beatmap.Value.BeatmapInfo.ID == beatmap.ID)
+ return false;
+
+ osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
+ isSelected = true;
+ return true;
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ if (isSelected)
+ return false;
+ this.FadeColour(Color4.Yellow, 100);
+ return true;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ if (isSelected)
+ return;
+ this.FadeColour(Color4.White, 100);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
+ {
+ this.osuGame = osuGame;
+ this.beatmaps = beatmaps;
+
+ var working = beatmaps.GetWorkingBeatmap(beatmap);
+ text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]";
+
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ if (isSelected)
+ this.FadeColour(Color4.White, 100);
+ isSelected = false;
+ }
+ }
+ }
+
+ private class PerformanceList : CompositeDrawable
+ {
+ private readonly FillFlowContainer scores;
+ private APIAccess api;
+
+ public PerformanceList()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = scores = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4)
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame, APIAccess api)
+ {
+ this.api = api;
+
+ if (!api.IsLoggedIn)
+ {
+ InternalChild = new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = "Please login to see online scores",
+ };
+ }
+
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private GetScoresRequest lastRequest;
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ lastRequest?.Cancel();
+ scores.Clear();
+
+ if (!api.IsLoggedIn)
+ return;
+
+ lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo);
+ lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
+ api.Queue(lastRequest);
+ }
+
+ private class PerformanceDisplay : CompositeDrawable
+ {
+ private readonly OsuSpriteText text;
+
+ private readonly Score score;
+ private readonly Beatmap beatmap;
+
+ public PerformanceDisplay(Score score, Beatmap beatmap)
+ {
+ this.score = score;
+ this.beatmap = beatmap;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = text = new OsuSpriteText();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
+ var calculator = ruleset.CreatePerformanceCalculator(beatmap, score);
+ if (calculator == null)
+ return;
+
+ var attributes = new Dictionary();
+ double performance = calculator.Calculate(attributes);
+
+ text.Text = $"{score.User.Username} -> online: {score.PP:n2}pp | local: {performance:n2}pp";
+ }
+ }
+ }
+
+ private class StarRatingGrid : CompositeDrawable
+ {
+ private readonly FillFlowContainer modFlow;
+ private readonly OsuSpriteText totalText;
+ private readonly FillFlowContainer categoryTexts;
+
+ public StarRatingGrid()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ modFlow = new FillFlowContainer
+ {
+ Name = "Checkbox flow",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(4, 4)
+ },
+ new FillFlowContainer
+ {
+ Name = "Information display",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(0, 4),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ totalText = new OsuSpriteText { TextSize = 24 },
+ categoryTexts = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical
+ }
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame)
+ {
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private Cached informationCache = new Cached();
+
+ private Ruleset ruleset;
+ private WorkingBeatmap beatmap;
+
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ beatmap = newBeatmap;
+
+ modFlow.Clear();
+
+ ruleset = newBeatmap.BeatmapInfo.Ruleset.CreateInstance();
+ foreach (var mod in ruleset.GetAllMods())
+ {
+ var checkBox = new OsuCheckbox
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = 50,
+ LabelText = mod.ShortenedName
+ };
+
+ checkBox.Current.ValueChanged += v => informationCache.Invalidate();
+ modFlow.Add(checkBox);
+ }
+
+ informationCache.Invalidate();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (ruleset == null)
+ return;
+
+ if (!informationCache.IsValid)
+ {
+ totalText.Text = string.Empty;
+ categoryTexts.Clear();
+
+ var allMods = ruleset.GetAllMods().ToList();
+ Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
+
+ var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
+ if (diffCalc != null)
+ {
+ var categories = new Dictionary();
+ double totalSr = diffCalc.Calculate(categories);
+
+ totalText.Text = $"Star rating: {totalSr:n2}";
+ foreach (var kvp in categories)
+ categoryTexts.Add(new OsuSpriteText { Text = $"{kvp.Key}: {kvp.Value:n2}" });
+ }
+
+ informationCache.Validate();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index cef85b65f1..d9951e002b 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -17,7 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
- public abstract class TestCasePlayer : OsuTestCase
+ public abstract class TestCasePlayer : ScreenTestCase
{
private readonly Type ruleset;
@@ -44,12 +44,17 @@ namespace osu.Game.Tests.Visual
{
RelativeSizeAxes = Framework.Graphics.Axes.Both,
Colour = Color4.Black,
+ Depth = int.MaxValue
});
string instantiation = ruleset?.AssemblyQualifiedName;
foreach (var r in rulesets.AvailableRulesets.Where(rs => instantiation == null || rs.InstantiationInfo == instantiation))
- AddStep(r.Name, () => loadPlayerFor(r));
+ {
+ Player p = null;
+ AddStep(r.Name, () => p = loadPlayerFor(r));
+ AddUntilStep(() => p.IsLoaded);
+ }
}
protected virtual Beatmap CreateBeatmap()
@@ -63,7 +68,7 @@ namespace osu.Game.Tests.Visual
return beatmap;
}
- private void loadPlayerFor(RulesetInfo r)
+ private Player loadPlayerFor(RulesetInfo r)
{
var beatmap = CreateBeatmap();
@@ -77,19 +82,21 @@ namespace osu.Game.Tests.Visual
if (Player != null)
Remove(Player);
- Add(Player = CreatePlayer(working, instance));
+ var player = CreatePlayer(working, instance);
+
+ LoadComponentAsync(player, LoadScreen);
+
+ return player;
}
- protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset)
+ protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) => new Player
{
- return new Player
- {
- InitialBeatmap = beatmap
- };
- }
+ InitialBeatmap = beatmap,
+ AllowPause = false
+ };
private const string test_beatmap_data =
-@"osu file format v14
+ @"osu file format v14
[General]
AudioLeadIn: 500
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 1f235e3893..d056afcf54 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -17,10 +17,11 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Users
{
- public class UserPanel : ClickableContainer, IHasContextMenu
+ public class UserPanel : OsuClickableContainer, IHasContextMenu
{
private readonly User user;
private const float height = 100;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 87c8275512..ccd1bd03dc 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -271,6 +271,9 @@
+
+
+
20171019041408_InitialCreate.cs
@@ -366,7 +369,7 @@
-
+
@@ -629,6 +632,7 @@
+
@@ -784,6 +788,8 @@
+
+