diff --git a/COMPILING.md b/COMPILING.md new file mode 100644 index 0000000000..bfcbf6bc2c --- /dev/null +++ b/COMPILING.md @@ -0,0 +1,36 @@ +# Linux +### 1. Requirements: +Mono >= 5.4.0 (>= 5.8.0 recommended) +Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release. +NuGet >= 4.4.0 +msbuild +git + +### 2. Cloning project +Clone the entire repository with submodules using +``` +git clone https://github.com/ppy/osu --recursive +``` +Then restore NuGet packages from the repository +``` +nuget restore +``` +### 3. Compiling +Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`. +### 4. Optimizing +If you want additional performance you can change build type to Release with +``` +msbuild -p:Configuration=Release +``` +Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running +``` +mono --aot ./osu\!.exe +``` +### 5. Troubleshooting +You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run +``` +nuget +sudo nuget update -self +``` +**Warning** NuGet creates few config files when it's run for the first time. +Do not run NuGet as root on the first run or you might run into very peculiar issues. diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index 95f993e510..0000000000 --- a/NuGet.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 856536d22d..47df86f57e 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,11 @@ This is still heavily under development and is not intended for end-user use. Th # Requirements -- A desktop platform which can compile .NET 4.5 (tested on macOS, linux and windows). We recommend using [Visual Studio Code](https://code.visualstudio.com/) (all platforms) or [Visual Studio Community Edition](https://www.visualstudio.com/) (windows only), both of which are free. -- Make sure you initialise and keep submodules up-to-date. +- A desktop platform that can compile .NET 4.6.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. + +# Getting Started +- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`) +- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`) # Contributing diff --git a/osu-framework b/osu-framework index 08f85f9bf9..6134dafccb 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 08f85f9bf9a7376aec8dfcde8c7c96d267d8c295 +Subproject commit 6134dafccb3368dac96d837537325c04b89fb8ee diff --git a/osu-resources b/osu-resources index 4287ee8043..e01f71160f 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f +Subproject commit e01f71160fb9b3167efcd177c7d7dba9e5d36604 diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 9e13003c3f..c46b0e3d12 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -110,7 +110,7 @@ namespace osu.Desktop.Overlays // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) - Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000); + notificationOverlay.Post(new UpdateCompleteNotification(version)); } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e4e9807754..3cc4e7f943 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -135,8 +135,8 @@ $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 6b6361b578..4757b50c4c 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -6,7 +6,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - + diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index b90a06b94e..a617b65676 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects.Drawable { diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 8e496c3b0c..7d0d80a0ce 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Objects StartTime = lastTickTime, ComboColour = ComboColour, X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Catch.Objects StartTime = repeatStartTime + t, ComboColour = ComboColour, X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.Objects set { Curve.ControlPoints = value; } } - public List RepeatSamples { get; set; } = new List(); + public List> RepeatSamples { get; set; } = new List>(); public CurveType CurveType { diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 3826fd1129..3b9eacde9c 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -5,7 +5,6 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index b03c8d2eea..16c909e063 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -36,8 +36,8 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True diff --git a/osu.Game.Rulesets.Catch/packages.config b/osu.Game.Rulesets.Catch/packages.config index cde428acea..b39a85a382 100644 --- a/osu.Game.Rulesets.Catch/packages.config +++ b/osu.Game.Rulesets.Catch/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index d5a799b4ed..407d4db143 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The time to retrieve the sample info list from. /// - private SampleInfoList sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 270c264e0c..8251dea5f7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private SampleInfoList sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 3e9fc1ae27..8e832960df 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -1,6 +1,7 @@ // 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.Mania.MathUtils; using osu.Game.Rulesets.Objects; @@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy }; if (hold.Head.Samples == null) - hold.Head.Samples = new SampleInfoList(); + hold.Head.Samples = new List(); hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); diff --git a/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs b/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs index d7bfa9caa1..ded1bc17af 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs index a8d1b079eb..e369df6ae1 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { @@ -24,4 +24,4 @@ namespace osu.Game.Rulesets.Mania.Judgements } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index d326c6fc0a..4787a4977b 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { @@ -11,4 +11,4 @@ namespace osu.Game.Rulesets.Mania.Judgements protected override int NumericResultFor(HitResult result) => 20; } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 1f3b352da4..4762a98c40 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 7b207ca229..41d817a746 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 557fbf6ea8..8ed5d2b924 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -113,4 +114,4 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables UpdateJudgement(true); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 537246509b..aabfcafa85 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -8,6 +8,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 012137f555..140bab2225 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -6,7 +6,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index 1932038411..b5890b289f 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Timing; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 26181164f9..3393774a9c 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -36,8 +36,8 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True diff --git a/osu.Game.Rulesets.Mania/packages.config b/osu.Game.Rulesets.Mania/packages.config index cde428acea..b39a85a382 100644 --- a/osu.Game.Rulesets.Mania/packages.config +++ b/osu.Game.Rulesets.Mania/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 28b6a04376..cd9c3888df 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -4,7 +4,7 @@ using OpenTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Judgements { @@ -34,4 +34,4 @@ namespace osu.Game.Rulesets.Osu.Judgements public ComboResult Combo; } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs index 71349285b3..7b1f80f439 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -11,8 +11,11 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using OpenTK; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Mods { @@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModEasy : ModEasy { - } - public class OsuModHidden : ModHidden + public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects { public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override double ScoreMultiplier => 1.06; + + private const double fade_in_duration_multiplier = 0.4; + private const double fade_out_duration_multiplier = 0.3; + + private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var d in drawables.OfType()) + { + d.ApplyCustomUpdateState += ApplyHiddenState; + d.FadeInDuration = preEmpt * fade_in_duration_multiplier; + } + } + + protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + + var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration; + var fadeOutDuration = preEmpt * fade_out_duration_multiplier; + + // new duration from completed fade in to end (before fading out) + var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime; + + switch (drawable) + { + case DrawableHitCircle circle: + // we don't want to see the approach circle + circle.ApproachCircle.Hide(); + + // fade out immediately after fade in. + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + circle.FadeOut(fadeOutDuration); + break; + case DrawableSlider slider: + using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) + { + slider.Body.FadeOut(longFadeDuration, Easing.Out); + + // delay a bit less to let the sliderball fade out peacefully instead of having a hard cut + using (slider.BeginDelayedSequence(longFadeDuration - fadeOutDuration, true)) + slider.Ball.FadeOut(fadeOutDuration); + } + + break; + case DrawableSpinner spinner: + // hide elements we don't care about. + spinner.Disc.Hide(); + spinner.Ticks.Hide(); + spinner.Background.Hide(); + + using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) + { + spinner.FadeOut(fadeOutDuration); + + // speed up the end sequence accordingly + switch (state) + { + case ArmedState.Hit: + spinner.ScaleTo(spinner.Scale * 1.2f, fadeOutDuration * 2, Easing.Out); + break; + case ArmedState.Miss: + spinner.ScaleTo(spinner.Scale * 0.8f, fadeOutDuration * 2, Easing.In); + break; + } + + spinner.Expire(); + } + + break; + } + } } public class OsuModHardRock : ModHardRock, IApplicableToHitObject @@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods slider.ControlPoints = newControlPoints; slider.Curve?.Calculate(); // Recalculate the slider curve } - - public void ApplyToHitObjects(RulesetContainer rulesetContainer) - { - - } } public class OsuModSuddenDeath : ModSuddenDeath @@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModPerfect : ModPerfect { - } public class OsuModSpunOut : Mod diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a973d2b580..6220bbd120 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly NumberPiece number; private readonly GlowPiece glow; - public DrawableHitCircle(OsuHitObject h) : base(h) + public DrawableHitCircle(HitCircle h) : base(h) { Origin = Anchor.Centre; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, number = new NumberPiece { - Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(), + Text = (HitObject.ComboIndex + 1).ToString(), }, ring = new RingPiece(), flash = new FlashPiece(), @@ -88,25 +88,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdatePreemptState(); - ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT)); + ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT)); ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT); } protected override void UpdateCurrentState(ArmedState state) { - double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime; - - glow.Delay(duration).FadeOut(400); + glow.FadeOut(400); switch (state) { case ArmedState.Idle: - this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT); + this.Delay(TIME_PREEMPT).FadeOut(500); + Expire(true); + + // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. + LifetimeEnd = HitObject.StartTime + HitObject.HitWindowFor(HitResult.Miss); break; case ArmedState.Miss: ApproachCircle.FadeOut(50); - this.FadeOut(TIME_FADEOUT / 5); + this.FadeOut(100); Expire(); break; case ArmedState.Hit: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3e0c23a1ab..f5f0300ae1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public const float TIME_PREEMPT = 600; public const float TIME_FADEIN = 400; - public const float TIME_FADEOUT = 500; + + /// + /// The number of milliseconds used to fade in. + /// + public virtual double FadeInDuration { get; set; } = TIME_FADEIN; + + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) @@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected virtual void UpdatePreemptState() - { - this.FadeIn(TIME_FADEIN); - } + protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration); protected virtual void UpdateCurrentState(ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 7755a54e88..f16a41519e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using OpenTK; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -24,4 +24,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.LoadComplete(); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index a9b63ea642..477ced01c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -24,19 +25,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.repeatPoint = repeatPoint; this.drawableSlider = drawableSlider; - AutoSizeAxes = Axes.Both; + Size = new Vector2(32 * repeatPoint.Scale); + Blending = BlendingMode.Additive; Origin = Anchor.Centre; - Scale = new Vector2(0.5f); Children = new Drawable[] { new SpriteIcon { - Icon = FontAwesome.fa_eercast, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(32), + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_eercast } }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index befe84e3e9..5a8bcae277 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -10,6 +10,7 @@ using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -17,23 +18,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { private readonly Slider slider; - private readonly DrawableHitCircle initialCircle; + public readonly DrawableHitCircle InitialCircle; private readonly List components = new List(); private readonly Container ticks; private readonly Container repeatPoints; - private readonly SliderBody body; - private readonly SliderBall ball; + public readonly SliderBody Body; + public readonly SliderBall Ball; - public DrawableSlider(Slider s) : base(s) + public DrawableSlider(Slider s) + : base(s) { slider = s; Children = new Drawable[] { - body = new SliderBody(s) + Body = new SliderBody(s) { AccentColour = AccentColour, Position = s.StackedPosition, @@ -41,16 +43,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, ticks = new Container(), repeatPoints = new Container(), - ball = new SliderBall(s) + Ball = new SliderBall(s) { Scale = new Vector2(s.Scale), AccentColour = AccentColour, AlwaysPresent = true, Alpha = 0 }, - initialCircle = new DrawableHitCircle(new HitCircle + InitialCircle = new DrawableHitCircle(new HitCircle { - //todo: avoid creating this temporary HitCircle. StartTime = s.StartTime, Position = s.StackedPosition, ComboIndex = s.ComboIndex, @@ -61,16 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }) }; - components.Add(body); - components.Add(ball); + components.Add(Body); + components.Add(Ball); - AddNested(initialCircle); + AddNested(InitialCircle); var repeatDuration = s.Curve.Distance / s.Velocity; foreach (var tick in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); + var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableTick = new DrawableSliderTick(tick) @@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var repeatPoint in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); + var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) @@ -105,11 +106,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private int currentRepeat; public bool Tracking; + public override double FadeInDuration + { + get { return base.FadeInDuration; } + set { InitialCircle.FadeInDuration = base.FadeInDuration = value; } + } + protected override void Update() { base.Update(); - Tracking = ball.Tracking; + Tracking = Ball.Tracking; double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); @@ -117,18 +124,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables progress = slider.ProgressAt(progress); if (repeat > currentRepeat) - { - if (repeat < slider.RepeatCount && ball.Tracking) - PlaySamples(); currentRepeat = repeat; - } //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!initialCircle.Judgements.Any(j => j.IsHit)) - initialCircle.Position = slider.Curve.PositionAt(progress); + if (!InitialCircle.Judgements.Any(j => j.IsHit)) + InitialCircle.Position = slider.Curve.PositionAt(progress); foreach (var c in components) c.UpdateProgress(progress, repeat); - foreach (var t in ticks.Children) t.Tracking = ball.Tracking; + foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) @@ -137,13 +140,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1; var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit)); - if (initialCircle.Judgements.Any(j => j.IsHit)) + if (InitialCircle.Judgements.Any(j => j.IsHit)) judgementsHit++; var hitFraction = (double)judgementsHit / judgementsCount; - if (hitFraction == 1 && initialCircle.Judgements.Any(j => j.Result == HitResult.Great)) + if (hitFraction == 1 && InitialCircle.Judgements.Any(j => j.Result == HitResult.Great)) AddJudgement(new OsuJudgement { Result = HitResult.Great }); - else if (hitFraction >= 0.5 && initialCircle.Judgements.Any(j => j.Result >= HitResult.Good)) + else if (hitFraction >= 0.5 && InitialCircle.Judgements.Any(j => j.Result >= HitResult.Good)) AddJudgement(new OsuJudgement { Result = HitResult.Good }); else if (hitFraction > 0) AddJudgement(new OsuJudgement { Result = HitResult.Meh }); @@ -154,26 +157,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateCurrentState(ArmedState state) { - ball.FadeIn(); + Ball.FadeIn(); using (BeginDelayedSequence(slider.Duration, true)) { - body.FadeOut(160); - ball.FadeOut(160); + Body.FadeOut(160); + Ball.FadeOut(160); this.FadeOut(800) .Expire(); } } - public Drawable ProxiedLayer => initialCircle.ApproachCircle; + public Drawable ProxiedLayer => InitialCircle.ApproachCircle; - public override Vector2 SelectionPoint => ToScreenSpace(body.Position); - public override Quad SelectionQuad => body.PathDrawQuad; - } - - internal interface ISliderProgress - { - void UpdateProgress(double progress, int repeat); + public override Vector2 SelectionPoint => ToScreenSpace(Body.Position); + public override Quad SelectionQuad => Body.PathDrawQuad; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 7199691ae6..bce7ef6141 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -8,6 +8,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 054a2067ec..5351ad50c4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Screens.Ranking; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -20,13 +21,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { private readonly Spinner spinner; - private readonly SpinnerDisc disc; - private readonly SpinnerTicks ticks; + public readonly SpinnerDisc Disc; + public readonly SpinnerTicks Ticks; private readonly SpinnerSpmCounter spmCounter; private readonly Container mainContainer; - private readonly SpinnerBackground background; + public readonly SpinnerBackground Background; private readonly Container circleContainer; private readonly CirclePiece circle; private readonly GlowPiece glow; @@ -84,20 +85,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - background = new SpinnerBackground + Background = new SpinnerBackground { Alpha = 0.6f, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - disc = new SpinnerDisc(spinner) + Disc = new SpinnerDisc(spinner) { Scale = Vector2.Zero, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, circleContainer.CreateProxy(), - ticks = new SpinnerTicks + Ticks = new SpinnerTicks { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -114,22 +115,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } - public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); protected override void CheckForJudgements(bool userTriggered, double timeOffset) { if (Time.Current < HitObject.StartTime) return; - if (Progress >= 1 && !disc.Complete) + if (Progress >= 1 && !Disc.Complete) { - disc.Complete = true; + Disc.Complete = true; const float duration = 200; - disc.FadeAccent(completeColour, duration); + Disc.FadeAccent(completeColour, duration); - background.FadeAccent(completeColour, duration); - background.FadeOut(duration); + Background.FadeAccent(completeColour, duration); + Background.FadeOut(duration); circle.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration); @@ -153,20 +154,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { normalColour = baseColour; - background.AccentColour = normalColour; + Background.AccentColour = normalColour; completeColour = colours.YellowLight.Opacity(0.75f); - disc.AccentColour = fillColour; + Disc.AccentColour = fillColour; circle.Colour = colours.BlueDark; glow.Colour = colours.BlueDark; } protected override void Update() { - disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); - if (!spmCounter.IsPresent && disc.Tracking) - spmCounter.FadeIn(TIME_FADEIN); + Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + if (!spmCounter.IsPresent && Disc.Tracking) + spmCounter.FadeIn(FadeInDuration); base.Update(); } @@ -175,14 +176,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - circle.Rotation = disc.Rotation; - ticks.Rotation = disc.Rotation; - spmCounter.SetRotation(disc.RotationAbsolute); + circle.Rotation = Disc.Rotation; + Ticks.Rotation = Disc.Rotation; + spmCounter.SetRotation(Disc.RotationAbsolute); float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; - disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); + Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); - symbol.RotateTo(disc.Rotation / 2, 500, Easing.OutQuint); + symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); } protected override void UpdatePreemptState() @@ -192,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(spinner.Scale * 0.3f); circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); - disc.RotateTo(-720); + Disc.RotateTo(-720); symbol.RotateTo(-720); mainContainer diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index ca75a61738..9f54ce3fa3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; + public override bool IsPresent => true; // handle input when hidden + public SpinnerDisc(Spinner s) { spinner = s; diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs new file mode 100644 index 0000000000..cb0d177a60 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Objects +{ + public interface ISliderProgress + { + void UpdateProgress(double progress, int repeat); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 7532387aa2..a3a6527b31 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,7 +7,7 @@ using OpenTK; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4f5a44e61d..ec51a10345 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List RepeatSamples { get; set; } = new List(); + public List> RepeatSamples { get; set; } = new List>(); public int RepeatCount { get; set; } = 1; private int stackHeight; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Objects StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", @@ -151,28 +151,22 @@ namespace osu.Game.Rulesets.Osu.Objects private void createRepeatPoints() { - var length = Curve.Distance; - var repeatPointDistance = Math.Min(Distance, length); - var repeatDuration = length / Velocity; + var repeatDuration = Distance / Velocity; for (var repeat = 1; repeat < RepeatCount; repeat++) { - for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) - { - var repeatStartTime = StartTime + repeat * repeatDuration; - var distanceProgress = d / length; + var repeatStartTime = StartTime + repeat * repeatDuration; - AddNested(new RepeatPoint - { - RepeatIndex = repeat, - StartTime = repeatStartTime, - Position = Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - ComboColour = ComboColour, - Samples = new SampleInfoList(RepeatSamples[repeat]) - }); - } + AddNested(new RepeatPoint + { + RepeatIndex = repeat, + StartTime = repeatStartTime, + Position = Curve.PositionAt(repeat % 2), + StackHeight = StackHeight, + Scale = Scale, + ComboColour = ComboColour, + Samples = new List(RepeatSamples[repeat]) + }); } } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index f82c6ce3b2..ba774e887f 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -9,9 +9,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using System; using System.Diagnostics; using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays { diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index 2cf321da50..38c602bc42 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Scoring 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"]); + count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]); + count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]); + count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]); + countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index ad9737af52..7520e1801c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -33,8 +32,7 @@ namespace osu.Game.Rulesets.Osu.Scoring foreach (var obj in beatmap.HitObjects) { - var slider = obj as Slider; - if (slider != null) + if (obj is Slider slider) { // Head AddJudgement(new OsuJudgement { Result = HitResult.Great }); @@ -64,10 +62,10 @@ namespace osu.Game.Rulesets.Osu.Scoring { base.PopulateScore(score); - score.Statistics[@"300"] = scoreResultCounts.GetOrDefault(HitResult.Great); - score.Statistics[@"100"] = scoreResultCounts.GetOrDefault(HitResult.Good); - score.Statistics[@"50"] = scoreResultCounts.GetOrDefault(HitResult.Meh); - score.Statistics[@"x"] = scoreResultCounts.GetOrDefault(HitResult.Miss); + score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great); + score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good); + score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh); + score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss); } protected override void OnNewJudgement(Judgement judgement) diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs new file mode 100644 index 0000000000..cdce19ad21 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs @@ -0,0 +1,116 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; +using osu.Game.Rulesets.Osu.Mods; +using OpenTK.Graphics; +using osu.Game.Rulesets.Osu.Judgements; +using System.Collections.Generic; +using System; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseHitCircle : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HitCircle), + typeof(OsuModHidden), + typeof(DrawableHitCircle) + }; + + private readonly Container content; + protected override Container Content => content; + + private bool auto; + private bool hidden; + private int depthIndex; + private int circleSize; + private float circleScale = 1; + + public TestCaseHitCircle() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Single", () => testSingle()); + AddStep("Stream", testStream); + AddToggleStep("Auto", v => auto = v); + AddToggleStep("Hidden", v => hidden = v); + AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); + AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); + } + + private void testSingle(double timeOffset = 0, Vector2? positionOffset = null) + { + positionOffset = positionOffset ?? Vector2.Zero; + + var circle = new HitCircle + { + StartTime = Time.Current + 1000 + timeOffset, + Position = positionOffset.Value, + ComboColour = Color4.LightSeaGreen + }; + + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); + + var drawable = new TestDrawableHitCircle(circle, auto) + { + Anchor = Anchor.Centre, + Scale = new Vector2(circleScale), + Depth = depthIndex++ + }; + + if (auto) + drawable.State.Value = ArmedState.Hit; + + if (hidden) + new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + + Add(drawable); + } + + private void testStream() + { + Vector2 pos = Vector2.Zero; + + for (int i = 0; i <= 1000; i += 100) + { + testSingle(i, pos); + pos += new Vector2(10); + } + } + + private class TestDrawableHitCircle : DrawableHitCircle + { + private readonly bool auto; + + public TestDrawableHitCircle(HitCircle h, bool auto) : base(h) + { + this.auto = auto; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && timeOffset > 0) + { + // pretend we really hit it + AddJudgement(new OsuJudgement + { + Result = HitObject.ScoreResultForOffset(timeOffset) + }); + } + base.CheckForJudgements(userTriggered, timeOffset); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs deleted file mode 100644 index c4932d7803..0000000000 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs +++ /dev/null @@ -1,129 +0,0 @@ -// 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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - [Ignore("getting CI working")] - public class TestCaseHitObjects : OsuTestCase - { - private FramedClock framedClock; - - private bool auto; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - var rateAdjustClock = new StopwatchClock(true); - framedClock = new FramedClock(rateAdjustClock); - - AddStep(@"circles", () => loadHitobjects(HitObjectType.Circle)); - AddStep(@"slider", () => loadHitobjects(HitObjectType.Slider)); - AddStep(@"spinner", () => loadHitobjects(HitObjectType.Spinner)); - - AddToggleStep("Auto", state => { auto = state; loadHitobjects(mode); }); - AddSliderStep("Playback speed", 0.0, 2.0, 0.5, v => rateAdjustClock.Rate = v); - - framedClock.ProcessFrame(); - - var clockAdjustContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Clock = framedClock, - Children = new[] - { - playfieldContainer = new OsuInputManager(rulesets.GetRuleset(0)) { RelativeSizeAxes = Axes.Both }, - approachContainer = new Container { RelativeSizeAxes = Axes.Both } - } - }; - - Add(clockAdjustContainer); - } - - private HitObjectType mode = HitObjectType.Slider; - - private Container playfieldContainer; - private Container approachContainer; - - private void loadHitobjects(HitObjectType mode) - { - this.mode = mode; - - switch (mode) - { - case HitObjectType.Circle: - const int count = 10; - - for (int i = 0; i < count; i++) - { - var h = new HitCircle - { - StartTime = framedClock.CurrentTime + 600 + i * 80, - Position = new Vector2((i - count / 2) * 14), - }; - - add(new DrawableHitCircle(h)); - } - break; - case HitObjectType.Slider: - add(new DrawableSlider(new Slider - { - StartTime = framedClock.CurrentTime + 600, - ControlPoints = new List - { - new Vector2(-200, 0), - new Vector2(400, 0), - }, - Distance = 400, - Position = new Vector2(-200, 0), - Velocity = 1, - TickDistance = 100, - })); - break; - case HitObjectType.Spinner: - add(new DrawableSpinner(new Spinner - { - StartTime = framedClock.CurrentTime + 600, - EndTime = framedClock.CurrentTime + 1600, - Position = new Vector2(0, 0), - })); - break; - } - } - - private int depth; - - private void add(DrawableOsuHitObject h) - { - h.Anchor = Anchor.Centre; - h.Depth = depth++; - - if (auto) - h.State.Value = ArmedState.Hit; - - playfieldContainer.Add(h); - var proxyable = h as IDrawableHitObjectWithProxiedApproach; - if (proxyable != null) - approachContainer.Add(proxyable.ProxiedLayer.CreateProxy()); - } - - private enum HitObjectType - { - Circle, - Slider, - Spinner - } - } -} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs new file mode 100644 index 0000000000..5b6b357351 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -0,0 +1,146 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; +using osu.Game.Rulesets.Osu.Mods; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseSlider : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Slider), + typeof(HitCircle), + typeof(OsuModHidden), + typeof(DrawableSlider), + typeof(DrawableHitCircle), + typeof(DrawableSliderTick), + typeof(DrawableRepeatPoint) + }; + + private readonly Container content; + protected override Container Content => content; + + private bool hidden; + private int repeats; + private int depthIndex; + private int circleSize; + private float circleScale = 1; + private double speedMultiplier = 2; + private double sliderMultiplier = 2; + + public TestCaseSlider() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Single", () => testSingle()); + AddStep("Stream", testStream); + AddStep("Repeated", () => testRepeated(repeats)); + AddToggleStep("Hidden", v => hidden = v); + AddSliderStep("Repeats", 1, 10, 1, s => repeats = s); + AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); + AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); + AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s); + AddSliderStep("SliderMultiplier", 0.1, 10, 2, s => sliderMultiplier = s); + } + + private void testSingle(double timeOffset = 0, Vector2? positionOffset = null) + { + positionOffset = positionOffset ?? Vector2.Zero; + + var slider = new Slider + { + StartTime = Time.Current + 1000 + timeOffset, + Position = new Vector2(-200, 0) + positionOffset.Value, + ComboColour = Color4.LightSeaGreen, + ControlPoints = new List + { + new Vector2(-200, 0) + positionOffset.Value, + new Vector2(400, 0) + positionOffset.Value, + }, + Distance = 400 + }; + + addSlider(slider); + } + + private void testRepeated(int repeats) + { + // The first run through the slider is considered a repeat + repeats++; + + var repeatSamples = new List>(); + for (int i = 0; i < repeats; i++) + repeatSamples.Add(new List()); + + var slider = new Slider + { + StartTime = Time.Current + 1000, + Position = new Vector2(-200, 0), + ComboColour = Color4.LightSeaGreen, + ControlPoints = new List + { + new Vector2(-200, 0), + new Vector2(400, 0), + }, + Distance = 400, + RepeatCount = repeats, + RepeatSamples = repeatSamples + }; + + addSlider(slider); + } + + private void testStream() + { + Vector2 pos = Vector2.Zero; + + for (int i = 0; i <= 1000; i += 100) + { + testSingle(i, pos); + pos += new Vector2(10); + } + } + + private void addSlider(Slider slider) + { + var cpi = new ControlPointInfo(); + cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + + var difficulty = new BeatmapDifficulty + { + SliderMultiplier = (float)sliderMultiplier, + CircleSize = circleSize + }; + + slider.ApplyDefaults(cpi, difficulty); + + var drawable = new DrawableSlider(slider) + { + Anchor = Anchor.Centre, + Scale = new Vector2(circleScale), + Depth = depthIndex++ + }; + + if (hidden) + new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + + Add(drawable); + } + } + +} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs new file mode 100644 index 0000000000..c4ee56455a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs @@ -0,0 +1,66 @@ +// 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 OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseSpinner : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] +{ + typeof(Spinner), + typeof(OsuModHidden), + typeof(DrawableSpinner) + }; + + private readonly Container content; + protected override Container Content => content; + + private bool hidden; + private int depthIndex; + private int circleSize; + private float circleScale = 1; + + public TestCaseSpinner() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Single", testSingle); + AddToggleStep("Hidden", v => hidden = v); + AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); + AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); + } + + private void testSingle() + { + var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; + + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); + + var drawable = new DrawableSpinner(spinner) + { + Anchor = Anchor.Centre, + Scale = new Vector2(circleScale), + Depth = depthIndex++ + }; + + if (hidden) + new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + + Add(drawable); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 7c9cbd63fc..f37b87e533 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -35,16 +35,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { - var circle = h as HitCircle; - if (circle != null) + if (h is HitCircle circle) return new DrawableHitCircle(circle); - var slider = h as Slider; - if (slider != null) + if (h is Slider slider) return new DrawableSlider(slider); - var spinner = h as Spinner; - if (spinner != null) + if (h is Spinner spinner) return new DrawableSpinner(spinner); return null; } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index ec71869adb..785c3e17fb 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -37,8 +37,8 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True @@ -75,6 +75,7 @@ + @@ -86,8 +87,10 @@ - + + + diff --git a/osu.Game.Rulesets.Osu/packages.config b/osu.Game.Rulesets.Osu/packages.config index cde428acea..b39a85a382 100644 --- a/osu.Game.Rulesets.Osu/packages.config +++ b/osu.Game.Rulesets.Osu/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs new file mode 100644 index 0000000000..982b339d3a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -0,0 +1,46 @@ +// 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.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Taiko.Audio +{ + public class DrumSampleMapping + { + private readonly ControlPointInfo controlPoints; + private readonly Dictionary mappings = new Dictionary(); + + public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio) + { + this.controlPoints = controlPoints; + + IEnumerable samplePoints; + if (controlPoints.SamplePoints.Count == 0) + // Get the default sample point + samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; + else + samplePoints = controlPoints.SamplePoints; + + foreach (var s in samplePoints) + { + mappings[s.Time] = new DrumSample + { + Centre = s.GetSampleInfo().GetChannel(audio.Sample, "Taiko"), + Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample, "Taiko") + }; + } + } + + public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; + + public class DrumSample + { + public SampleChannel Centre; + public SampleChannel Rim; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9b4a6c47a9..690b80b12c 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - SampleInfoList samples = obj.Samples; + List samples = obj.Samples; bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); @@ -115,12 +115,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List allSamples = curveData != null ? curveData.RepeatSamples : new List(new[] { samples }); + List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - SampleInfoList currentSamples = allSamples[i]; + List currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index c9daef8c99..ce5be8d148 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Judgements { @@ -20,4 +20,4 @@ namespace osu.Game.Rulesets.Taiko.Judgements } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 3cd134f3f7..70cdd1fe0e 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Judgements { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 75e988ced6..f5bafefd0b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index e662f61bbe..a741e35963 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index df33aae94e..0c10c7142e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; @@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables validKeyPressed = HitActions.Contains(action); // Only count this as handled if the new judgement is a hit - return UpdateJudgement(true) && Judgements.LastOrDefault()?.IsHit == true; + return UpdateJudgement(true); } protected override void Update() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index 81b23af1a9..249bb41d91 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > second_hit_window) - AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Miss }); + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None }); return; } @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return false; // Assume the intention was to hit the strong hit with both keys only if the first key is still being held down - return firstKeyHeld && UpdateJudgement(true) && Judgements.LastOrDefault()?.IsHit == true; + return firstKeyHeld && UpdateJudgement(true); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 738902846b..26e6585fb9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -14,6 +14,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -34,10 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; - private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim }; - private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; - private TaikoAction[] lastAction; - /// /// The amount of times the user has hit this swell. /// @@ -205,19 +202,20 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } + private bool? lastWasCentre; + public override bool OnPressed(TaikoAction action) { // Don't handle keys before the swell starts if (Time.Current < HitObject.StartTime) return false; - // Find the keyset which this key corresponds to - var keySet = rimActions.Contains(action) ? rimActions : centreActions; + var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre; - // Ensure alternating keysets - if (keySet == lastAction) + // Ensure alternating centre and rim hits + if (lastWasCentre == isCentre) return false; - lastAction = keySet; + lastWasCentre = isCentre; UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 7976cbbbc1..cc7dd2fa0f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -6,6 +6,9 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; +using System.Linq; +using osu.Game.Audio; +using System.Collections.Generic; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -35,6 +38,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; } + // Normal and clap samples are handled by the drum + protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); + + protected override string SampleNamespace => "Taiko"; + protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); public abstract bool OnPressed(TaikoAction action); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 4104b59979..a39d627cc4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,8 +3,6 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Linq; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -74,13 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Objects FirstTick = first, TickSpacing = tickSpacing, StartTime = t, - IsStrong = IsStrong, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) + IsStrong = IsStrong }); first = false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index f74a543ca9..cd2876ea2d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -16,4 +16,4 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index df1a19267f..c43899ebf1 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays { foreach (var tick in drumRoll.NestedHitObjects.OfType()) { - Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); + Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2)); hitButton = !hitButton; } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index df9ce5e2eb..3848e36fc9 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs new file mode 100644 index 0000000000..172c6e9d84 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs @@ -0,0 +1,44 @@ +// 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 OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [Ignore("getting CI working")] + public class TestCaseInputDrum : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(InputDrum), + typeof(DrumSampleMapping), + typeof(SampleInfo), + typeof(SampleControlPoint) + }; + + public TestCaseInputDrum() + { + Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200), + Child = new InputDrum(new ControlPointInfo()) + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs index bca4806108..1f13864c2a 100644 --- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Taiko.UI; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; using OpenTK; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Tests { @@ -165,11 +166,15 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addSwell(double duration = default_duration) { - rulesetContainer.Playfield.Add(new DrawableSwell(new Swell + var swell = new Swell { StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, Duration = duration, - })); + }; + + swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + rulesetContainer.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -184,6 +189,8 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = duration, }; + d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); } @@ -195,6 +202,8 @@ namespace osu.Game.Rulesets.Taiko.Tests IsStrong = strong }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + if (strong) rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); else @@ -209,6 +218,8 @@ namespace osu.Game.Rulesets.Taiko.Tests IsStrong = strong }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + if (strong) rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); else diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 41b66c286b..0b67613ec3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.UI { @@ -49,4 +50,4 @@ namespace osu.Game.Rulesets.Taiko.UI base.LoadComplete(); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 5e79166bec..9b2ea095d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -4,12 +4,15 @@ using System; using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Audio; 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.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Audio; namespace osu.Game.Rulesets.Taiko.UI { @@ -18,16 +21,26 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public InputDrum() + private const float middle_split = 0.025f; + + private readonly ControlPointInfo controlPoints; + + public InputDrum(ControlPointInfo controlPoints) { + this.controlPoints = controlPoints; + RelativeSizeAxes = Axes.Both; FillMode = FillMode.Fit; + } - const float middle_split = 0.025f; + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + var sampleMappings = new DrumSampleMapping(controlPoints, audio); Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, sampleMappings) { Name = "Left Half", Anchor = Anchor.Centre, @@ -38,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, sampleMappings) { Name = "Right Half", Anchor = Anchor.Centre, @@ -72,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + private readonly DrumSampleMapping sampleMappings; + + public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings) { + this.sampleMappings = sampleMappings; + Masking = true; Children = new Drawable[] @@ -128,15 +145,21 @@ namespace osu.Game.Rulesets.Taiko.UI Drawable target = null; Drawable back = null; + var drumSample = sampleMappings.SampleAt(Time.Current); + if (action == CentreAction) { target = centreHit; back = centre; + + drumSample.Centre?.Play(); } else if (action == RimAction) { target = rimHit; back = rim; + + drumSample.Rim?.Play(); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2cc95fc981..3fdbd056ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -16,17 +16,11 @@ using osu.Framework.Extensions.Color4Extensions; using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Framework.Input.Bindings; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using System.Collections.Generic; -using osu.Game.Audio; -using System; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoPlayfield : ScrollingPlayfield, IKeyBindingHandler + public class TaikoPlayfield : ScrollingPlayfield { /// /// Default height of a when inside a . @@ -61,13 +55,9 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Box overlayBackground; private readonly Box background; - private readonly ControlPointInfo controlPointInfo; - private Dictionary drumSampleMappings; - - public TaikoPlayfield(ControlPointInfo controlPointInfo) + public TaikoPlayfield(ControlPointInfo controlPoints) : base(Axes.X) { - this.controlPointInfo = controlPointInfo; AddRangeInternal(new Drawable[] { backgroundContainer = new Container @@ -160,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI { RelativeSizeAxes = Axes.Both, }, - new InputDrum + new InputDrum(controlPoints) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -205,19 +195,8 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + private void load(OsuColour colours) { - drumSampleMappings = new Dictionary(); - foreach (var s in controlPointInfo.SamplePoints) - { - drumSampleMappings.Add(s, - new DrumSamples - { - Centre = s.GetSampleInfo().GetChannel(audio.Sample), - Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample) - }); - } - overlayBackgroundContainer.BorderColour = colours.Gray0; overlayBackground.Colour = colours.Gray1; @@ -282,28 +261,5 @@ namespace osu.Game.Rulesets.Taiko.UI kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim)); } } - - public bool OnPressed(TaikoAction action) - { - var samplePoint = controlPointInfo.SamplePointAt(Clock.CurrentTime); - - if (!drumSampleMappings.TryGetValue(samplePoint, out var samples)) - throw new InvalidOperationException("Current sample set not found."); - - if (action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre) - samples.Centre.Play(); - else - samples.Rim.Play(); - - return true; - } - - public bool OnReleased(TaikoAction action) => false; - - private class DrumSamples - { - public SampleChannel Centre; - public SampleChannel Rim; - } } } diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index bb02db62b9..1aed86f8f9 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -36,14 +36,15 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True + @@ -82,6 +83,7 @@ + diff --git a/osu.Game.Rulesets.Taiko/packages.config b/osu.Game.Rulesets.Taiko/packages.config index cde428acea..b39a85a382 100644 --- a/osu.Game.Rulesets.Taiko/packages.config +++ b/osu.Game.Rulesets.Taiko/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 639befef74..c09b987407 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual testRemoveAll(); testEmptyTraversal(); + testHiding(); } private void ensureRandomFetchSuccess() => @@ -295,6 +296,40 @@ namespace osu.Game.Tests.Visual checkNoSelection(); } + private void testHiding() + { + var hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); + setSelected(1, 1); + + checkVisibleItemCount(true, 2); + advanceSelection(true); + checkSelected(1, 3); + + setHidden(3); + checkSelected(1, 1); + + setHidden(2, false); + advanceSelection(true); + checkSelected(1, 2); + + setHidden(1); + checkSelected(1, 2); + + setHidden(2); + checkNoSelection(); + + void setHidden(int diff, bool hidden = true) + { + AddStep((hidden ? "" : "un") + $"hide diff {diff}", () => + { + hidingSet.Beatmaps[diff - 1].Hidden = hidden; + carousel.UpdateBeatmapSet(hidingSet); + }); + } + } + private BeatmapSetInfo createTestBeatmapSet(int i) { return new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 0168cedc86..3a50e43239 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -1,69 +1,161 @@ // 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 OpenTK; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { public class TestCaseBeatmapInfoWedge : OsuTestCase { - private BeatmapManager beatmaps; - private readonly Random random; - private readonly BeatmapInfoWedge infoWedge; + private RulesetStore rulesets; + private TestBeatmapInfoWedge infoWedge; + private readonly List beatmaps = new List(); private readonly Bindable beatmap = new Bindable(); - public TestCaseBeatmapInfoWedge() + [BackgroundDependencyLoader] + private void load(OsuGameBase game, RulesetStore rulesets) { - random = new Random(0123); + this.rulesets = rulesets; - Add(infoWedge = new BeatmapInfoWedge + beatmap.BindTo(game.Beatmap); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(infoWedge = new TestBeatmapInfoWedge { Size = new Vector2(0.5f, 245), RelativeSizeAxes = Axes.X, - Margin = new MarginPadding - { - Top = 20, - }, + Margin = new MarginPadding { Top = 20 } }); AddStep("show", () => { - Content.FadeInFromZero(250); infoWedge.State = Visibility.Visible; infoWedge.UpdateBeatmap(beatmap); }); - AddStep("hide", () => + + AddWaitStep(3); + + AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); + + AddWaitStep(3); + + AddStep("show", () => { infoWedge.State = Visibility.Visible; }); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) { - infoWedge.State = Visibility.Hidden; - Content.FadeOut(100); + var ruleset = rulesetInfo.CreateInstance(); + beatmaps.Add(createTestBeatmap(rulesetInfo)); + + var name = rulesetInfo.ShortName; + selectBeatmap(name); + + // TODO: adjust cases once more info is shown for other gamemodes + switch (ruleset) + { + case OsuRuleset osu: + testOsuBeatmap(osu); + testInfoLabels(5); + break; + default: + testInfoLabels(2); + break; + } + } + + testNullBeatmap(); + } + + private void testOsuBeatmap(OsuRuleset ruleset) + { + AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version"); + AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist"); + AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author")); + } + + private void testInfoLabels(int expectedCount) + { + AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); + AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); + } + + private void testNullBeatmap() + { + selectNullBeatmap(); + AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); + AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); + } + + private void selectBeatmap(string name) + { + var infoBefore = infoWedge.Info; + + AddStep($"select {name} beatmap", () => + { + beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name)); + infoWedge.UpdateBeatmap(beatmap); }); - AddStep("random beatmap", randomBeatmap); - AddStep("null beatmap", () => infoWedge.UpdateBeatmap(beatmap.Default)); + + AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); } - [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps) + private void selectNullBeatmap() { - this.beatmaps = beatmaps; - beatmap.BindTo(game.Beatmap); + AddStep("select null beatmap", () => + { + beatmap.Value = beatmap.Default; + infoWedge.UpdateBeatmap(beatmap); + }); } - private void randomBeatmap() + private Beatmap createTestBeatmap(RulesetInfo ruleset) { - var sets = beatmaps.GetAllUsableBeatmapSets(); - if (sets.Count == 0) - return; + List objects = new List(); + for (double i = 0; i < 50000; i += 1000) + objects.Add(new HitObject { StartTime = i }); - var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; - beatmap.Value = beatmaps.GetWorkingBeatmap(b); - infoWedge.UpdateBeatmap(beatmap); + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + AuthorString = $"{ruleset.ShortName}Author", + Artist = $"{ruleset.ShortName}Artist", + Source = $"{ruleset.ShortName}Source", + Title = $"{ruleset.ShortName}Title" + }, + Ruleset = ruleset, + StarDifficulty = 6, + Version = $"{ruleset.ShortName}Version" + }, + HitObjects = objects + }; + } + + private class TestBeatmapInfoWedge : BeatmapInfoWedge + { + public new BufferedWedgeInfo Info => base.Info; } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index cef8797f20..ad15833569 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -160,9 +160,9 @@ namespace osu.Game.Tests.Visual }; foreach(var s in scores) { - s.Statistics.Add("300", RNG.Next(2000)); - s.Statistics.Add("100", RNG.Next(2000)); - s.Statistics.Add("50", RNG.Next(2000)); + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } anotherScores = new[] @@ -272,9 +272,9 @@ namespace osu.Game.Tests.Visual }; foreach (var s in anotherScores) { - s.Statistics.Add("300", RNG.Next(2000)); - s.Statistics.Add("100", RNG.Next(2000)); - s.Statistics.Add("50", RNG.Next(2000)); + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } topScore = new OnlineScore @@ -299,9 +299,9 @@ namespace osu.Game.Tests.Visual TotalScore = 987654321, Accuracy = 0.8487, }; - topScore.Statistics.Add("300", RNG.Next(2000)); - topScore.Statistics.Add("100", RNG.Next(2000)); - topScore.Statistics.Add("50", RNG.Next(2000)); + topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); + topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); + topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index bd5772d3bb..87552c3f17 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => failOverlay.Show()); - AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnHover(null)); + AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null)); AddStep("Hide overlay", () => failOverlay.Hide()); AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual var secondButton = pauseOverlay.Buttons.Skip(1).First(); AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); AddAssert("Second button selected", () => secondButton.Selected); @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual var secondButton = pauseOverlay.Buttons.Skip(1).First(); - AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); AddAssert("Second button not selected", () => !secondButton.Selected); AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual var secondButton = pauseOverlay.Buttons.Skip(1).First(); - AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null)); AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 79d3d7d4ba..46deca073f 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -1,21 +1,32 @@ // 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; namespace osu.Game.Tests.Visual { - [TestFixture] public class TestCaseNotificationOverlay : OsuTestCase { private readonly NotificationOverlay manager; + private readonly List progressingNotifications = new List(); + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(NotificationSection), + typeof(SimpleNotification), + typeof(ProgressNotification), + typeof(ProgressCompletionNotification), + typeof(IHasCompletionTarget), + typeof(Notification) + }; public TestCaseNotificationOverlay() { @@ -24,33 +35,65 @@ namespace osu.Game.Tests.Visual Content.Add(manager = new NotificationOverlay { Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Origin = Anchor.TopRight }); - AddToggleStep(@"show", state => manager.State = state ? Visibility.Visible : Visibility.Hidden); + SpriteText displayedCount = new SpriteText(); - AddStep(@"simple #1", sendNotification1); - AddStep(@"simple #2", sendNotification2); - AddStep(@"progress #1", sendProgress1); - AddStep(@"progress #2", sendProgress2); - AddStep(@"barrage", () => sendBarrage()); + Content.Add(displayedCount); + + void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state); + void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); + + manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; + + + setState(Visibility.Visible); + AddStep(@"simple #1", sendHelloNotification); + AddStep(@"simple #2", sendAmazingNotification); + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); + + checkProgressingCount(2); + + setState(Visibility.Hidden); + + AddRepeatStep(@"add many simple", sendManyNotifications, 3); + AddWaitStep(5); + + checkProgressingCount(0); + + AddStep(@"progress #3", sendUploadProgress); + + checkProgressingCount(1); + + AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); + + AddWaitStep(10); + + checkProgressingCount(0); + + + setState(Visibility.Visible); + + //AddStep(@"barrage", () => sendBarrage()); } - private void sendBarrage(int remaining = 100) + private void sendBarrage(int remaining = 10) { switch (RNG.Next(0, 4)) { case 0: - sendNotification1(); + sendHelloNotification(); break; case 1: - sendNotification2(); + sendAmazingNotification(); break; case 2: - sendProgress1(); + sendUploadProgress(); break; case 3: - sendProgress2(); + sendDownloadProgress(); break; } @@ -66,7 +109,7 @@ namespace osu.Game.Tests.Visual if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) { - var p = progressingNotifications.FirstOrDefault(n => n.IsAlive && n.State == ProgressNotificationState.Queued); + var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued); if (p != null) p.State = ProgressNotificationState.Active; } @@ -74,13 +117,13 @@ namespace osu.Game.Tests.Visual foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) { if (n.Progress < 1) - n.Progress += (float)(Time.Elapsed / 2000) * RNG.NextSingle(); + n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); else n.State = ProgressNotificationState.Completed; } } - private void sendProgress2() + private void sendDownloadProgress() { var n = new ProgressNotification { @@ -91,9 +134,7 @@ namespace osu.Game.Tests.Visual progressingNotifications.Add(n); } - private readonly List progressingNotifications = new List(); - - private void sendProgress1() + private void sendUploadProgress() { var n = new ProgressNotification { @@ -104,14 +145,20 @@ namespace osu.Game.Tests.Visual progressingNotifications.Add(n); } - private void sendNotification2() + private void sendAmazingNotification() { manager.Post(new SimpleNotification { Text = @"You are amazing" }); } - private void sendNotification1() + private void sendHelloNotification() { manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); } + + private void sendManyNotifications() + { + for (int i = 0; i < 10; i++) + manager.Post(new SimpleNotification { Text = @"Spam incoming!!" }); + } } } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 6435df7c2c..18e40db064 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; private DependencyContainer dependencies; + private WorkingBeatmap defaultBeatmap; public override IReadOnlyList RequiredTypes => new[] { @@ -47,31 +48,61 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + private class TestSongSelect : PlaySongSelect + { + public WorkingBeatmap CurrentBeatmap => Beatmap.Value; + public new BeatmapCarousel Carousel => base.Carousel; + } + [BackgroundDependencyLoader] private void load(BeatmapManager baseManager) { - PlaySongSelect songSelect; + TestSongSelect songSelect = null; - if (manager == null) + var storage = new TestStorage(@"TestCasePlaySongSelect"); + + // this is by no means clean. should be replacing inside of OsuGameBase somehow. + var context = new OsuDbContext(); + + Func contextFactory = () => context; + + dependencies.Cache(rulesets = new RulesetStore(contextFactory)); + dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) { - var storage = new TestStorage(@"TestCasePlaySongSelect"); + DefaultBeatmap = defaultBeatmap = baseManager.GetWorkingBeatmap(null) + }); - // this is by no means clean. should be replacing inside of OsuGameBase somehow. - var context = new OsuDbContext(); + void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => + { + if (deleteMaps) manager.DeleteAll(); - Func contextFactory = () => context; - - dependencies.Cache(rulesets = new RulesetStore(contextFactory)); - dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) + if (songSelect != null) { - DefaultBeatmap = baseManager.GetWorkingBeatmap(null) - }); + Remove(songSelect); + songSelect.Dispose(); + } + Add(songSelect = new TestSongSelect()); + }); + + loadNewSongSelect(true); + + AddWaitStep(3); + + AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + + AddStep("import test maps", () => + { for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); - } + }); - Add(songSelect = new PlaySongSelect()); + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + + loadNewSongSelect(); + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/TestCasePopupDialog.cs new file mode 100644 index 0000000000..ed9c47a253 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePopupDialog.cs @@ -0,0 +1,37 @@ +// 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.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePopupDialog : OsuTestCase + { + public TestCasePopupDialog() + { + var popup = new PopupDialog + { + RelativeSizeAxes = Axes.Both, + State = Framework.Graphics.Containers.Visibility.Visible, + Icon = FontAwesome.fa_assistive_listening_systems, + HeaderText = @"This is a test popup", + BodyText = "I can say lots of stuff and even wrap my words!", + Buttons = new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = @"Yes. That you can.", + }, + new PopupDialogOkButton + { + Text = @"You're a fake!", + }, + } + }; + + Add(popup); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index d0c5aa4939..28ce9524e0 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -15,6 +15,15 @@ namespace osu.Game.Tests.Visual { private BeatmapManager beatmaps; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Score), + typeof(Results), + typeof(ResultsPage), + typeof(ResultsPageScore), + typeof(ResultsPageRanking) + }; + [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps) { @@ -41,12 +50,12 @@ namespace osu.Game.Tests.Visual MaxCombo = 123, Rank = ScoreRank.A, Date = DateTimeOffset.Now, - Statistics = new Dictionary + Statistics = new Dictionary { - { "300", 50 }, - { "100", 20 }, - { "50", 50 }, - { "x", 1 } + { HitResult.Great, 50 }, + { HitResult.Good, 20 }, + { HitResult.Meh, 50 }, + { HitResult.Miss, 1 } }, User = new User { diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs new file mode 100644 index 0000000000..9f538af09b --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs @@ -0,0 +1,39 @@ +// 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.Framework.Graphics.Containers; +using osu.Game.Overlays.Toolbar; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseToolbar : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ToolbarButton), + typeof(ToolbarModeSelector), + typeof(ToolbarModeButton), + typeof(ToolbarNotificationButton), + }; + + public TestCaseToolbar() + { + var toolbar = new Toolbar { State = Visibility.Visible }; + + Add(toolbar); + + var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + + void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); + + setNotifications(1); + setNotifications(2); + setNotifications(3); + setNotifications(0); + setNotifications(144); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 38d59f03b5..13b6509740 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual { public class TestCaseUserProfile : OsuTestCase { + private readonly TestUserProfileOverlay profile; + public override IReadOnlyList RequiredTypes => new[] { typeof(ProfileHeader), @@ -23,8 +25,12 @@ namespace osu.Game.Tests.Visual public TestCaseUserProfile() { - var profile = new UserProfileOverlay(); - Add(profile); + Add(profile = new TestUserProfileOverlay()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); AddStep("Show offline dummy", () => profile.ShowUser(new User { @@ -48,6 +54,9 @@ namespace osu.Game.Tests.Visual Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() } }, false)); + + checkSupporterTag(false); + AddStep("Show ppy", () => profile.ShowUser(new User { Username = @"peppy", @@ -55,6 +64,9 @@ namespace osu.Game.Tests.Visual Country = new Country { FullName = @"Australia", FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); + + checkSupporterTag(true); + AddStep("Show flyte", () => profile.ShowUser(new User { Username = @"flyte", @@ -62,8 +74,23 @@ namespace osu.Game.Tests.Visual Country = new Country { FullName = @"Japan", FlagName = @"JP" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); + AddStep("Hide", profile.Hide); AddStep("Show without reload", profile.Show); } + + private void checkSupporterTag(bool isSupporter) + { + AddUntilStep(() => profile.Header.User != null, "wait for load"); + if (isSupporter) + AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); + else + AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); + } + + private class TestUserProfileOverlay : UserProfileOverlay + { + public new ProfileHeader Header => base.Header; + } } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index ff012bb6e2..8c04874e75 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -37,8 +37,8 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True @@ -134,6 +134,7 @@ + @@ -148,6 +149,7 @@ + diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index e09f2a07ba..9fbb0537bc 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -6,6 +6,6 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - + \ No newline at end of file diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 64a9aa50a0..71975bf0fa 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -14,10 +14,20 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; - public SampleChannel GetChannel(SampleManager manager) + public SampleChannel GetChannel(SampleManager manager, string resourceNamespace = null) { - var channel = manager.Get($"Gameplay/{Bank}-{Name}"); - channel.Volume.Value = Volume / 100.0; + SampleChannel channel = null; + + if (resourceNamespace != null) + channel = manager.Get($"Gameplay/{resourceNamespace}/{Bank}-{Name}"); + + // try without namespace as a fallback. + if (channel == null) + channel = manager.Get($"Gameplay/{Bank}-{Name}"); + + if (channel != null) + channel.Volume.Value = Volume / 100.0; + return channel; } diff --git a/osu.Game/Audio/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs deleted file mode 100644 index 06dd716a4a..0000000000 --- a/osu.Game/Audio/SampleInfoList.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Audio -{ - public class SampleInfoList : List - { - public SampleInfoList() - { - } - - public SampleInfoList(IEnumerable elements) : base(elements) - { - } - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0325785016..c86860f7b0 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -697,10 +697,12 @@ namespace osu.Game.Beatmaps } } + public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null; + /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public void ImportFromStable() + public async Task ImportFromStable() { var stable = GetStableStorage?.Invoke(); @@ -710,7 +712,7 @@ namespace osu.Game.Beatmaps return; } - Import(stable.GetDirectories("Songs")); + await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning); } public void DeleteAll() diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 40e45da13c..c2c13e1909 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The default sample volume at this control point. /// - public int SampleVolume; + public int SampleVolume = 100; /// /// Create a SampleInfo based on the sample settings in this control point. diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 1434943da0..3ec83ed8d5 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -21,8 +21,7 @@ namespace osu.Game.Beatmaps Metadata = new BeatmapMetadata { Artist = "please load a beatmap!", - Title = "no beatmaps available!", - AuthorString = "no one", + Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 065c770738..14605081b6 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -122,26 +122,26 @@ namespace osu.Game.Online.API.Requests { foreach (var kvp in value) { - string key = kvp.Key; - switch (key) + HitResult newKey; + switch (kvp.Key) { case @"count_300": - key = @"300"; + newKey = HitResult.Great; break; case @"count_100": - key = @"100"; + newKey = HitResult.Good; break; case @"count_50": - key = @"50"; + newKey = HitResult.Meh; break; case @"count_miss": - key = @"x"; + newKey = HitResult.Miss; break; default: continue; } - Statistics.Add(key, kvp.Value); + Statistics.Add(newKey, kvp.Value); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4745733bd9..2bc32794d7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -16,6 +16,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using OpenTK; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Input.Bindings; using osu.Framework.Platform; @@ -37,7 +38,7 @@ namespace osu.Game private MusicController musicController; - private NotificationOverlay notificationOverlay; + private NotificationOverlay notifications; private DialogOverlay dialogOverlay; @@ -64,6 +65,8 @@ namespace osu.Game public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + public readonly BindableBool ShowOverlays = new BindableBool(); + private OsuScreen screenStack; private VolumeControl volume; @@ -136,7 +139,7 @@ namespace osu.Game if (s.Beatmap == null) { - notificationOverlay.Post(new SimpleNotification + notifications.Post(new SimpleNotification { Text = @"Tried to load a score for a beatmap we don't have!", Icon = FontAwesome.fa_life_saver, @@ -154,7 +157,7 @@ namespace osu.Game base.LoadComplete(); // hook up notifications to components. - BeatmapManager.PostNotification = n => notificationOverlay?.Post(n); + BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; AddRange(new Drawable[] @@ -207,8 +210,9 @@ namespace osu.Game Origin = Anchor.TopRight, }, overlayContent.Add); - loadComponentSingleFile(notificationOverlay = new NotificationOverlay + loadComponentSingleFile(notifications = new NotificationOverlay { + GetToolbarHeight = () => ToolbarOffset, Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -219,15 +223,7 @@ namespace osu.Game Depth = -6, }, overlayContent.Add); - Logger.NewEntry += entry => - { - if (entry.Level < LogLevel.Important) return; - - notificationOverlay.Post(new SimpleNotification - { - Text = $@"{entry.Level}: {entry.Message}" - }); - }; + forwardLoggedErrorsToNotifications(); dependencies.Cache(settings); dependencies.Cache(social); @@ -236,7 +232,7 @@ namespace osu.Game dependencies.Cache(userProfile); dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); - dependencies.Cache(notificationOverlay); + dependencies.Cache(notifications); dependencies.Cache(dialogOverlay); // ensure only one of these overlays are open at once. @@ -271,22 +267,69 @@ namespace osu.Game }; } - settings.StateChanged += delegate + void updateScreenOffset() { - switch (settings.State) + float offset = 0; + + if (settings.State == Visibility.Visible) + offset += ToolbarButton.WIDTH / 2; + if (notifications.State == Visibility.Visible) + offset -= ToolbarButton.WIDTH / 2; + + screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + } + + settings.StateChanged += _ => updateScreenOffset(); + notifications.StateChanged += _ => updateScreenOffset(); + + notifications.Enabled.BindTo(ShowOverlays); + + ShowOverlays.ValueChanged += visible => + { + //central game screen change logic. + if (!visible) { - case Visibility.Hidden: - intro.MoveToX(0, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); - break; - case Visibility.Visible: - intro.MoveToX(SettingsOverlay.SIDEBAR_WIDTH / 2, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); - break; + hideAllOverlays(); + musicController.State = Visibility.Hidden; + Toolbar.State = Visibility.Hidden; } + else + Toolbar.State = Visibility.Visible; }; Cursor.State = Visibility.Hidden; } + private void forwardLoggedErrorsToNotifications() + { + int recentErrorCount = 0; + + const double debounce = 5000; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Error || entry.Target == null) return; + + if (recentErrorCount < 2) + { + notifications.Post(new SimpleNotification + { + Icon = FontAwesome.fa_bomb, + Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", + Activated = () => + { + Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + return true; + } + }); + } + + Interlocked.Increment(ref recentErrorCount); + + Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce); + }; + } + private Task asyncLoadStream; private void loadComponentSingleFile(T d, Action add) @@ -335,8 +378,6 @@ namespace osu.Game public bool OnReleased(GlobalAction action) => false; - public event Action ScreenChanged; - private Container mainContent; private Container overlayContent; @@ -351,30 +392,7 @@ namespace osu.Game direct.State = Visibility.Hidden; social.State = Visibility.Hidden; userProfile.State = Visibility.Hidden; - notificationOverlay.State = Visibility.Hidden; - } - - private void screenChanged(Screen newScreen) - { - currentScreen = newScreen as OsuScreen; - - if (currentScreen == null) - { - Exit(); - return; - } - - //central game screen change logic. - if (!currentScreen.ShowOverlays) - { - hideAllOverlays(); - musicController.State = Visibility.Hidden; - Toolbar.State = Visibility.Hidden; - } - else - Toolbar.State = Visibility.Visible; - - ScreenChanged?.Invoke(newScreen); + notifications.State = Visibility.Hidden; } protected override bool OnExiting() @@ -422,15 +440,18 @@ namespace osu.Game private void screenAdded(Screen newScreen) { + currentScreen = (OsuScreen)newScreen; + newScreen.ModePushed += screenAdded; newScreen.Exited += screenRemoved; - - screenChanged(newScreen); } private void screenRemoved(Screen newScreen) { - screenChanged(newScreen); + currentScreen = (OsuScreen)newScreen; + + if (newScreen == null) + Exit(); } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0ddff5e5aa..ea0bf22112 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -177,8 +177,7 @@ namespace osu.Game } catch (MigrationFailedException e) { - Logger.Log((e.InnerException ?? e).ToString(), LoggingTarget.Database, LogLevel.Error); - Logger.Log("Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database, LogLevel.Error); + Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); // if we failed, let's delete the database and start fresh. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index bd108a193b..b4aea898b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; @@ -176,7 +177,7 @@ namespace osu.Game.Overlays.BeatmapSet Shadow = false, Margin = new MarginPadding { Top = 20 }, }, - textFlow = new TextFlowContainer + textFlow = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 5a3aba7b43..2d5913d8ca 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -48,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Font = @"Exo2.0-RegularItalic", Margin = new MarginPadding { Left = side_margin } }, - new DrawableFlag(score.User.Country?.FlagName) + new DrawableFlag(score.User.Country) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -104,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Text = $"{score.Statistics["300"]}/{score.Statistics["100"]}/{score.Statistics["50"]}", + Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", Font = @"Exo2.0-RegularItalic", Margin = new MarginPadding { Right = side_margin } }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 833ed94c0f..e3b878587d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -52,13 +52,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores score = value; avatar.User = username.User = score.User; - flag.FlagName = score.User.Country?.FlagName; + flag.Country = score.User.Country; date.Text = $@"achieved {score.Date:MMM d, yyyy}"; rank.UpdateRank(score.Rank); totalScore.Value = $@"{score.TotalScore:N0}"; accuracy.Value = $@"{score.Accuracy:P2}"; - statistics.Value = $"{score.Statistics["300"]}/{score.Statistics["100"]}/{score.Statistics["50"]}"; + statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; modsContainer.Clear(); foreach (Mod mod in score.Mods) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 9b19b8150e..d2bd50cad6 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog private readonly FillFlowContainer buttonsContainer; private readonly SpriteIcon icon; private readonly SpriteText header; - private readonly SpriteText body; + private readonly TextFlowContainer body; public FontAwesome Icon { @@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Dialog public string BodyText { - get { return body.Text; } set { body.Text = value; } } @@ -220,17 +219,15 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - Text = @"Header", TextSize = 25, Shadow = true, }, - body = new OsuSpriteText + body = new OsuTextFlowContainer(t => t.TextSize = 18) { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Text = @"Body", - TextSize = 18, - Shadow = true, + Padding = new MarginPadding(15), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, }, }, }, diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 53d77dab6c..c6be428987 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Scale = new Vector2(1f / scale_when_full), }, - description = new TextFlowContainer + description = new OsuTextFlowContainer { TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 77b7c3add2..35c2e9234d 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -204,13 +204,13 @@ namespace osu.Game.Overlays.Mods { iconsContainer.AddRange(new[] { - backgroundIcon = new ModIcon(Mods[1]) + backgroundIcon = new PassThroughTooltipModIcon(Mods[1]) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, Position = new Vector2(1.5f), }, - foregroundIcon = new ModIcon(Mods[0]) + foregroundIcon = new PassThroughTooltipModIcon(Mods[0]) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Mods } else { - iconsContainer.Add(foregroundIcon = new ModIcon(Mod) + iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod) { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -259,5 +259,14 @@ namespace osu.Game.Overlays.Mods Mod = mod; } + + private class PassThroughTooltipModIcon : ModIcon + { + public override string TooltipText => null; + + public PassThroughTooltipModIcon(Mod mod) : base(mod) + { + } + } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 260214a14f..5744e8c189 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,6 +9,10 @@ using osu.Game.Overlays.Notifications; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; +using System; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -19,9 +22,39 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private ScrollContainer scrollContainer; + /// + /// Whether posted notifications should be processed. + /// + public readonly BindableBool Enabled = new BindableBool(true); + private FlowContainer sections; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + public NotificationOverlay() + { + ScheduledDelegate notificationsEnabler = null; + Enabled.ValueChanged += v => + { + if (!IsLoaded) + { + processingPosts = v; + return; + } + + notificationsEnabler?.Cancel(); + + if (v) + // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, 1000); + else + processingPosts = false; + }; + } + [BackgroundDependencyLoader] private void load() { @@ -36,12 +69,12 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, - Alpha = 0.6f, + Alpha = 0.6f }, - scrollContainer = new OsuScrollContainer + new OsuScrollContainer { + Masking = true, RelativeSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT }, Children = new[] { sections = new FillFlowContainer @@ -55,14 +88,14 @@ namespace osu.Game.Overlays { Title = @"Notifications", ClearText = @"Clear All", - AcceptTypes = new[] { typeof(SimpleNotification) }, + AcceptTypes = new[] { typeof(SimpleNotification) } }, new NotificationSection { Title = @"Running Tasks", ClearText = @"Cancel All", - AcceptTypes = new[] { typeof(ProgressNotification) }, - }, + AcceptTypes = new[] { typeof(ProgressNotification) } + } } } } @@ -70,47 +103,59 @@ namespace osu.Game.Overlays }; } + private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); + private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); + + public readonly BindableInt UnreadCount = new BindableInt(); + private int runningDepth; private void notificationClosed() - { - // hide ourselves if all notifications have been dismissed. - if (sections.Select(c => c.DisplayedCount).Sum() == 0) - State = Visibility.Hidden; - } - - public void Post(Notification notification) { Schedule(() => { - State = Visibility.Visible; - - ++runningDepth; - notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; - - notification.Closed += notificationClosed; - - var hasCompletionTarget = notification as IHasCompletionTarget; - if (hasCompletionTarget != null) - hasCompletionTarget.CompletionTarget = Post; - - var ourType = notification.GetType(); - sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification); + // hide ourselves if all notifications have been dismissed. + if (totalCount == 0) + State = Visibility.Hidden; }); + + updateCounts(); + } + + private readonly Scheduler postScheduler = new Scheduler(); + + private bool processingPosts = true; + + public void Post(Notification notification) => postScheduler.Add(() => + { + ++runningDepth; + notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; + + notification.Closed += notificationClosed; + + var hasCompletionTarget = notification as IHasCompletionTarget; + if (hasCompletionTarget != null) + hasCompletionTarget.CompletionTarget = Post; + + var ourType = notification.GetType(); + sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification); + + updateCounts(); + }); + + protected override void Update() + { + base.Update(); + if (processingPosts) + postScheduler.Update(); } protected override void PopIn() { base.PopIn(); - scrollContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH / 2); - } - - private void markAllRead() - { - sections.Children.ForEach(s => s.MarkAllRead()); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); } protected override void PopOut() @@ -120,7 +165,26 @@ namespace osu.Game.Overlays markAllRead(); this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH / 2); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + } + + private void updateCounts() + { + UnreadCount.Value = unreadCount; + } + + private void markAllRead() + { + sections.Children.ForEach(s => s.MarkAllRead()); + + updateCounts(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 422051364e..dc2dcf2d74 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -91,7 +91,6 @@ namespace osu.Game.Overlays.Notifications AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Top = 5, Left = 45, Right = 30 }, @@ -261,4 +260,4 @@ namespace osu.Game.Overlays.Notifications } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 09768ba0ea..42fcc3aa0f 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -15,7 +15,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Notifications { - public class NotificationSection : FillFlowContainer + public class NotificationSection : AlwaysUpdateFillFlowContainer { private OsuSpriteText titleText; private OsuSpriteText countText; @@ -26,11 +26,14 @@ namespace osu.Game.Overlays.Notifications public int DisplayedCount => notifications.Count(n => !n.WasClosed); + public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); + public void Add(Notification notification) => notifications.Add(notification); public IEnumerable AcceptTypes; private string clearText; + public string ClearText { get { return clearText; } @@ -108,7 +111,7 @@ namespace osu.Game.Overlays.Notifications }, }, }, - notifications = new FillFlowContainer + notifications = new AlwaysUpdateFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -157,4 +160,13 @@ namespace osu.Game.Overlays.Notifications notifications?.Children.ForEach(n => n.Read = true); } } -} \ No newline at end of file + + public class AlwaysUpdateFillFlowContainer : FillFlowContainer + where T : Drawable + { + // this is required to ensure correct layout and scheduling on children. + // the layout portion of this is being tracked as a framework issue (https://github.com/ppy/osu-framework/issues/1297). + protected override bool RequiresChildrenUpdate => true; + } + +} diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 12c7fe64ba..d797372390 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using OpenTK; using OpenTK.Graphics; @@ -94,8 +95,8 @@ namespace osu.Game.Overlays.Notifications protected virtual void Completed() { - Expire(); CompletionTarget?.Invoke(CreateCompletionNotification()); + base.Close(); } public override bool DisplayOnTop => false; @@ -114,7 +115,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.Both, }); - Content.Add(textDrawable = new TextFlowContainer(t => + Content.Add(textDrawable = new OsuTextFlowContainer(t => { t.TextSize = 16; }) diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index daf1ac838d..1e691e2a5a 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using OpenTK; namespace osu.Game.Overlays.Notifications @@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Notifications } }); - Content.Add(textDrawable = new TextFlowContainer(t => t.TextSize = 16) + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) { Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, @@ -82,9 +83,11 @@ namespace osu.Game.Overlays.Notifications set { + if (value == base.Read) return; + base.Read = value; - Light.FadeTo(value ? 1 : 0, 100); + Light.FadeTo(value ? 0 : 1, 100); } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index dcab942522..ce0feeb4c6 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays { @@ -63,7 +64,7 @@ namespace osu.Game.Overlays Width = 240, RelativeSizeAxes = Axes.Y, }, - textLine1 = new SpriteText + textLine1 = new OsuSpriteText { Padding = new MarginPadding(10), Font = @"Exo2.0-Black", @@ -72,7 +73,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - textLine2 = new SpriteText + textLine2 = new OsuSpriteText { TextSize = 24, Font = @"Exo2.0-Light", @@ -97,7 +98,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both }, - textLine3 = new SpriteText + textLine3 = new OsuSpriteText { Padding = new MarginPadding { Bottom = 15 }, Font = @"Exo2.0-Bold", diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index a706799664..960bb60287 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -29,7 +29,8 @@ namespace osu.Game.Overlays.Profile private readonly FillFlowContainer scoreText, scoreNumberText; private readonly RankGraph rankGraph; - private readonly Container coverContainer, supporterTag; + public readonly SupporterIcon SupporterTag; + private readonly Container coverContainer; private readonly Sprite levelBadge; private readonly SpriteText levelText; private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; @@ -94,32 +95,13 @@ namespace osu.Game.Overlays.Profile AutoSizeAxes = Axes.Both, Children = new Drawable[] { - supporterTag = new CircularContainer + SupporterTag = new SupporterIcon { + Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Y = -75, - Size = new Vector2(25, 25), - Masking = true, - BorderThickness = 3, - BorderColour = Color4.White, - Alpha = 0, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - new SpriteIcon - { - Icon = FontAwesome.fa_heart, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(12), - } - } + Size = new Vector2(25, 25) }, new LinkFlowContainer.ProfileLink(user) { @@ -127,7 +109,7 @@ namespace osu.Game.Overlays.Profile Origin = Anchor.BottomLeft, Y = -48, }, - countryFlag = new DrawableFlag(user.Country?.FlagName) + countryFlag = new DrawableFlag(user.Country) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -328,7 +310,8 @@ namespace osu.Game.Overlays.Profile Depth = float.MaxValue, }, coverContainer.Add); - if (user.IsSupporter) supporterTag.Show(); + if (user.IsSupporter) + SupporterTag.Show(); if (!string.IsNullOrEmpty(user.Colour)) { @@ -350,7 +333,7 @@ namespace osu.Game.Overlays.Profile { infoTextLeft.AddText("from "); infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.FlagName = user.Country.FlagName; + countryFlag.Country = user.Country; } infoTextLeft.NewParagraph(); @@ -473,7 +456,7 @@ namespace osu.Game.Overlays.Profile Width = width, Height = 26 }); - Add(numberText = new SpriteText + Add(numberText = new OsuSpriteText { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 8a835634b8..904ed609e8 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -120,7 +121,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu } } }, - new TextFlowContainer(t => { t.TextSize = 19; }) + new OsuTextFlowContainer(t => { t.TextSize = 19; }) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Profile/SupporterIcon.cs b/osu.Game/Overlays/Profile/SupporterIcon.cs new file mode 100644 index 0000000000..b5cd94e54b --- /dev/null +++ b/osu.Game/Overlays/Profile/SupporterIcon.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Overlays.Profile +{ + public class SupporterIcon : CircularContainer, IHasTooltip + { + private readonly Box background; + + public string TooltipText => "osu!supporter"; + + public SupporterIcon() + { + Masking = true; + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Masking = true, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + new Triangles + { + TriangleScale = 0.2f, + ColourLight = OsuColour.FromHex(@"ff7db7"), + ColourDark = OsuColour.FromHex(@"de5b95"), + RelativeSizeAxes = Axes.Both, + Velocity = 0.3f, + }, + } + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_heart, + Scale = new Vector2(0.45f), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index e288445c6d..9ab4143613 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -30,8 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importButton.Enabled.Value = false; - Task.Factory.StartNew(beatmaps.ImportFromStable) - .ContinueWith(t => Schedule(() => importButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); + beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); } }, deleteButton = new DangerousSettingsButton diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 798fa00032..a80f6d4da8 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - public const float SIDEBAR_WIDTH = Sidebar.DEFAULT_WIDTH; + private const float sidebar_width = Sidebar.DEFAULT_WIDTH; protected const float WIDTH = 400; @@ -102,7 +102,7 @@ namespace osu.Game.Overlays if (showSidebar) { - AddInternal(Sidebar = new Sidebar { Width = SIDEBAR_WIDTH }); + AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); SectionsContainer.SelectedSection.ValueChanged += section => { @@ -167,7 +167,7 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - Sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = false; diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index ed206e7e1d..b0171feb30 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar SetIcon(FontAwesome.fa_comments); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(ChatOverlay chat) { StateContainer = chat; diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index 7d25440e2c..5c64ae69ec 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar SetIcon(FontAwesome.fa_osu_chevron_down_o); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct) { StateContainer = direct; diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 319dd63bc9..f38c772890 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Toolbar }; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(RulesetStore rulesets, OsuGame game) { foreach (var r in rulesets.AvailableRulesets) @@ -81,7 +81,10 @@ namespace osu.Game.Overlays.Toolbar ruleset.ValueChanged += rulesetChanged; ruleset.DisabledChanged += disabledChanged; - ruleset.BindTo(game.Ruleset); + if (game != null) + ruleset.BindTo(game.Ruleset); + else + ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(); } public override bool HandleInput => !ruleset.Disabled; diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index d150aacdf9..81c57a984f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.fa_music; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(MusicController music) { StateContainer = music; diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index e11a22d675..c093767e52 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -2,8 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Overlays.Toolbar { @@ -11,17 +17,96 @@ namespace osu.Game.Overlays.Toolbar { protected override Anchor TooltipAnchor => Anchor.TopRight; + public BindableInt NotificationCount = new BindableInt(); + + private readonly CountCircle countDisplay; + public ToolbarNotificationButton() { Icon = FontAwesome.fa_bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; + + Add(countDisplay = new CountCircle + { + Alpha = 0, + Height = 16, + RelativePositionAxes = Axes.Both, + Origin = Anchor.Centre, + Position = new Vector2(0.7f, 0.25f), + }); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(NotificationOverlay notificationOverlay) { StateContainer = notificationOverlay; + + if (notificationOverlay != null) + NotificationCount.BindTo(notificationOverlay.UnreadCount); + + NotificationCount.ValueChanged += count => + { + if (count == 0) + countDisplay.FadeOut(200, Easing.OutQuint); + else + { + countDisplay.Count = count; + countDisplay.FadeIn(200, Easing.OutQuint); + } + }; + } + + private class CountCircle : CompositeDrawable + { + private readonly OsuSpriteText countText; + private readonly Circle circle; + + private int count; + + public int Count + { + get { return count; } + set + { + if (count == value) + return; + + if (value > count) + { + circle.FlashColour(Color4.White, 600, Easing.OutQuint); + this.ScaleTo(1.1f).Then().ScaleTo(1, 600, Easing.OutElastic); + } + + count = value; + countText.Text = value.ToString("#,0"); + } + } + + public CountCircle() + { + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -1, + TextSize = 14, + Padding = new MarginPadding(5), + Colour = Color4.White, + UseFullGlyphHeight = true, + Font = "Exo2.0-Bold", + } + }; + } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 69fdd27d5d..59314b8771 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,8 +21,11 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; - Action = stateContainer.ToggleVisibility; - stateContainer.StateChanged += stateChanged; + if (stateContainer != null) + { + Action = stateContainer.ToggleVisibility; + stateContainer.StateChanged += stateChanged; + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index cf4f664e81..d0d76dd5d2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Toolbar TooltipSub = "Change your settings"; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(SettingsOverlay settings) { StateContainer = settings; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 234d6f0f9a..74d1da4384 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.fa_users; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(SocialOverlay chat) { StateContainer = chat; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 7374a9aa44..9aa660147a 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays private ProfileSection[] sections; private GetUserRequest userReq; private APIAccess api; - private ProfileHeader header; + protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; @@ -113,12 +113,12 @@ namespace osu.Game.Overlays Colour = OsuColour.Gray(0.2f) }); - header = new ProfileHeader(user); + Header = new ProfileHeader(user); Add(sectionsContainer = new SectionsContainer { RelativeSizeAxes = Axes.Both, - ExpandableHeader = header, + ExpandableHeader = Header, FixedHeader = tabs, HeaderBackground = new Box { @@ -169,7 +169,7 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - header.User = user; + Header.User = user; foreach (string id in user.ProfileOrder) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4487f74364..36740b96cb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit } catch (Exception e) { - Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error); + Logger.Error(e, "Could not load beatmap sucessfully!"); return; } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 5ab4b7636b..3d7880f56f 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Judgements { diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index d804111a7f..f8c9b9734f 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Judgements { diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs new file mode 100644 index 0000000000..1024d5686d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -0,0 +1,20 @@ +// 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.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToDrawableHitObjects + { + /// + /// Applies this to a list of s. + /// + /// The list of s to apply to. + void ApplyToDrawableHitObjects(IEnumerable drawables); + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d960ab6b48..45a7275c53 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Framework.Configuration; using OpenTK; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Drawables { @@ -72,6 +73,10 @@ namespace osu.Game.Rulesets.Objects.Drawables public IReadOnlyList Judgements => judgements; protected List Samples = new List(); + protected virtual IEnumerable GetSamples() => HitObject.Samples; + + // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first + protected virtual string SampleNamespace => null; public readonly Bindable State = new Bindable(); @@ -84,12 +89,14 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(AudioManager audio) { - if (HitObject.Samples != null) + var samples = GetSamples(); + if (samples.Any()) { if (HitObject.SampleControlPoint == null) - throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)} must always have an attached {nameof(HitObject.SampleControlPoint)}."); + throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - foreach (SampleInfo s in HitObject.Samples) + foreach (SampleInfo s in samples) { SampleInfo localSampleInfo = new SampleInfo { @@ -98,7 +105,7 @@ namespace osu.Game.Rulesets.Objects.Drawables Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume }; - SampleChannel channel = localSampleInfo.GetChannel(audio.Sample); + SampleChannel channel = localSampleInfo.GetChannel(audio.Sample, SampleNamespace); if (channel == null) continue; @@ -116,6 +123,9 @@ namespace osu.Game.Rulesets.Objects.Drawables { UpdateState(state); + // apply any custom state overrides + ApplyCustomUpdateState?.Invoke(this, state); + if (State == ArmedState.Hit) PlaySamples(); }; @@ -174,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { judgementOccurred = false; - if (AllJudged || State != ArmedState.Idle) + if (AllJudged) return false; if (NestedHitObjects != null) @@ -237,9 +247,15 @@ namespace osu.Game.Rulesets.Objects.Drawables h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); + h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s); nestedHitObjects.Add(h); } + /// + /// Bind to apply a custom state which can override the default implementation. + /// + public event Action ApplyCustomUpdateState; + protected abstract void UpdateState(ArmedState state); } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index e950516bf4..4f06f6afe1 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Objects /// public virtual double StartTime { get; set; } + private List samples; + /// /// The samples to be played when this hit object is hit. /// @@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Objects /// and can be treated as the default samples for the hit object. /// /// - public SampleInfoList Samples; + public List Samples + { + get => samples ?? (samples = new List()); + set => samples = value; + } [JsonIgnore] public SampleControlPoint SampleControlPoint; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 667f921e04..fbf02f5345 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 0d7d617405..bdbd7a9e65 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // Generate the final per-node samples - var nodeSamples = new List(nodes); + var nodeSamples = new List>(nodes); for (int i = 0; i <= repeatCount; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); /// /// Creates a legacy Spinner-type hit object. @@ -234,9 +234,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The hold end time. protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime); - private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { - var soundTypes = new SampleInfoList + var soundTypes = new List { new SampleInfo { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 698b74cc28..6dc8a07630 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Distance { get; set; } - public List RepeatSamples { get; set; } + public List> RepeatSamples { get; set; } public int RepeatCount { get; set; } = 1; public double EndTime => StartTime + RepeatCount * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 86dd40b06e..2060b84222 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 24c205db13..0062d29446 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; +using osu.Game.Audio; namespace osu.Game.Rulesets.Objects.Legacy.Osu { @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 0554cfd97d..529a28ac15 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; +using osu.Game.Audio; namespace osu.Game.Rulesets.Objects.Legacy.Taiko { @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 5abad2d661..2fe2424d49 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). /// - List RepeatSamples { get; } + List> RepeatSamples { get; } } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5cdf46ee46..3038c51e64 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -38,6 +38,13 @@ namespace osu.Game.Rulesets /// A ruleset, if available, else null. public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id); + /// + /// Retrieve a ruleset using a known short name. + /// + /// The ruleset's short name. + /// A ruleset, if available, else null. + public RulesetInfo GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); + /// /// All available rulesets. /// diff --git a/osu.Game/Rulesets/Objects/Drawables/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs similarity index 91% rename from osu.Game/Rulesets/Objects/Drawables/HitResult.cs rename to osu.Game/Rulesets/Scoring/HitResult.cs index 961843cbd7..49ab9fd2f0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Rulesets.Objects.Drawables +namespace osu.Game.Rulesets.Scoring { public enum HitResult { diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 6a06f364c6..025335ba55 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -40,6 +40,6 @@ namespace osu.Game.Rulesets.Scoring public DateTimeOffset Date; - public Dictionary Statistics = new Dictionary(); + public Dictionary Statistics = new Dictionary(); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e129a81116..23c4464bb1 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Scoring { diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5ca3d9521b..90f63aeab6 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI private readonly ModType type; - public string TooltipText { get; } + public virtual string TooltipText { get; } public ModIcon(Mod mod) { diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index fe7c0c05ed..40a37c689b 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -260,6 +260,9 @@ namespace osu.Game.Rulesets.UI } Playfield.PostProcess(); + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects); } protected override void Update() diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 367cf4337d..b2308aca71 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts if (Beatmap.Value == null) return; + if (Beatmap.Value.Track.Length == double.PositiveInfinity) return; + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 19d00f3477..76f51d1c33 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit { protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; private readonly Box bottomBackground; private readonly Container screenContainer; diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index ec2e8e0cb1..c96194f63d 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens { private bool showDisclaimer; - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; public Loader() { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index ce7856c5a9..c82d90d16c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -11,12 +11,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; -using osu.Game.Overlays.Toolbar; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; using osu.Framework.Audio.Sample; using osu.Framework.Audio; +using osu.Framework.Configuration; using osu.Framework.Threading; namespace osu.Game.Screens.Menu @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + private readonly BindableBool showOverlays = new BindableBool(); + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -34,8 +36,6 @@ namespace osu.Game.Screens.Menu public Action OnChart; public Action OnTest; - private Toolbar toolbar; - private readonly FlowContainerWithOrigin buttonFlow; //todo: make these non-internal somehow. @@ -131,9 +131,9 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGame game = null) + private void load(AudioManager audio, OsuGame game) { - toolbar = game?.Toolbar; + if (game != null) showOverlays.BindTo(game.ShowOverlays); sampleBack = audio.Sample.Get(@"Menu/button-back-select"); } @@ -300,7 +300,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { - toolbar?.Hide(); + showOverlays.Value = false; logo.ClearTransforms(targetMember: nameof(Position)); logo.RelativePositionAxes = Axes.Both; @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Menu logoTracking = true; logo.Impact(); - toolbar?.Show(); + showOverlays.Value = true; }, 200); break; default: diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 532ee71b72..d0ad613640 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Menu private readonly SpriteIcon icon; private Color4 iconColour; - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; public override bool HasLocalCursorDisplayed => true; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index d7beb34a2f..a6a1afa320 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Menu public override bool HasLocalCursorDisplayed => true; - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 90f68ba9f1..fac0ec1422 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Menu { private readonly ButtonSystem buttons; - public override bool ShowOverlays => buttons.State != MenuState.Initial; + public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial; private readonly BackgroundScreenDefault background; private Screen songSelect; diff --git a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs b/osu.Game/Screens/Multiplayer/ParticipantInfo.cs index fa48287ce1..2197b7477c 100644 --- a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs +++ b/osu.Game/Screens/Multiplayer/ParticipantInfo.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multiplayer set { host.Text = value.Username; - flagContainer.Children = new[] { new DrawableFlag(value.Country?.FlagName) { RelativeSizeAxes = Axes.Both } }; + flagContainer.Children = new[] { new DrawableFlag(value.Country) { RelativeSizeAxes = Axes.Both } }; } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 4a27c7f1ea..0013d1a882 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -28,7 +28,12 @@ namespace osu.Game.Screens /// protected virtual BackgroundScreen CreateBackground() => null; - public virtual bool ShowOverlays => true; + protected BindableBool ShowOverlays = new BindableBool(); + + /// + /// Whether overlays should be shown when this screen is entered or resumed. + /// + public virtual bool ShowOverlaysOnEnter => true; protected new OsuGameBase Game => base.Game as OsuGameBase; @@ -70,7 +75,10 @@ namespace osu.Game.Screens } if (osuGame != null) + { Ruleset.BindTo(osuGame.Ruleset); + ShowOverlays.BindTo(osuGame.ShowOverlays); + } sampleExit = audio.Sample.Get(@"UI/screen-back"); } @@ -94,6 +102,8 @@ namespace osu.Game.Screens base.OnResuming(last); logo.AppendAnimatingAction(() => LogoArriving(logo, true), true); sampleExit?.Play(); + + ShowOverlays.Value = ShowOverlaysOnEnter; } protected override void OnSuspending(Screen next) @@ -139,6 +149,8 @@ namespace osu.Game.Screens logo.AppendAnimatingAction(() => LogoArriving(logo, false), true); base.OnEntering(last); + + ShowOverlays.Value = ShowOverlaysOnEnter; } protected override bool OnExiting(Screen next) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 6969cd915b..094c0331f4 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -263,6 +263,14 @@ namespace osu.Game.Screens.Play private class Button : DialogButton { + protected override bool OnHover(InputState state) => true; + + protected override bool OnMouseMove(InputState state) + { + Selected.Value = true; + return base.OnMouseMove(state); + } + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (args.Repeat || args.Key != Key.Enter || !Selected) diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 06ef87276a..351db533f3 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -7,10 +7,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 340fc39d52..35f39e940f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; public override bool HasLocalCursorDisplayed => !pauseContainer.IsPaused && !HasFailed && RulesetContainer.ProvidingUserCursor; @@ -46,6 +46,8 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; private set; } public bool AllowPause { get; set; } = true; + public bool AllowLeadIn { get; set; } = true; + public bool AllowResults { get; set; } = true; public int RestartCount; @@ -125,7 +127,7 @@ namespace osu.Game.Screens.Play } catch (Exception e) { - Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error); + Logger.Error(e, "Could not load beatmap sucessfully!"); //couldn't load, hard abort! Exit(); @@ -136,7 +138,10 @@ namespace osu.Game.Screens.Play decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = RulesetContainer.Objects.First().StartTime; - decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))); + decoupledClock.Seek(AllowLeadIn + ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)) + : firstObjectTime); + decoupledClock.ProcessFrame(); offsetClock = new FramedOffsetClock(decoupledClock); @@ -273,6 +278,8 @@ namespace osu.Game.Screens.Play ValidForResume = false; + if (!AllowResults) return; + using (BeginDelayedSequence(1000)) { onCompletionEvent = Schedule(delegate diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index de67bef004..15a97096e7 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play private BeatmapMetadataDisplay info; private bool showOverlays = true; - public override bool ShowOverlays => showOverlays; + public override bool ShowOverlaysOnEnter => showOverlays; public override bool AllowBeatmapRulesetChange => false; @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, - new MetadataLine("Mapper", metadata.Author.Username) + new MetadataLine("Mapper", metadata.AuthorString) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 8e27cb235c..406887624c 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -17,6 +17,7 @@ using OpenTK.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Ranking { @@ -183,7 +184,7 @@ namespace osu.Game.Screens.Ranking Height = 50, Margin = new MarginPadding { Bottom = 110 }, }, - new SpriteText + new OsuSpriteText { Text = $"{score.MaxCombo}x", TextSize = 40, @@ -194,7 +195,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, }, - new SpriteText + new OsuSpriteText { Text = "max combo", TextSize = 20, @@ -204,7 +205,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.TopCentre, }, - new SpriteText + new OsuSpriteText { Text = $"{score.Accuracy:P2}", TextSize = 40, @@ -215,7 +216,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, }, - new SpriteText + new OsuSpriteText { Text = "accuracy", TextSize = 20, diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 911b688669..0d29b74dfd 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -23,6 +23,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions; namespace osu.Game.Screens.Ranking { @@ -163,7 +164,7 @@ namespace osu.Game.Screens.Ranking } }; - statisticsContainer.ChildrenEnumerable = Score.Statistics.Select(s => new DrawableScoreStatistic(s)); + statisticsContainer.ChildrenEnumerable = Score.Statistics.OrderByDescending(p => p.Key).Select(s => new DrawableScoreStatistic(s)); } protected override void LoadComplete() @@ -186,9 +187,9 @@ namespace osu.Game.Screens.Ranking private class DrawableScoreStatistic : Container { - private readonly KeyValuePair statistic; + private readonly KeyValuePair statistic; - public DrawableScoreStatistic(KeyValuePair statistic) + public DrawableScoreStatistic(KeyValuePair statistic) { this.statistic = statistic; @@ -201,15 +202,15 @@ namespace osu.Game.Screens.Ranking { Children = new Drawable[] { - new SpriteText { + new OsuSpriteText { Text = statistic.Value.ToString().PadLeft(4, '0'), Colour = colours.Gray7, TextSize = 30, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - new SpriteText { - Text = statistic.Key, + new OsuSpriteText { + Text = statistic.Key.GetDescription(), Colour = colours.Gray7, Font = @"Exo2.0-Bold", Y = 26, @@ -250,16 +251,16 @@ namespace osu.Game.Screens.Ranking { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Text = datetime.ToString("HH:mm"), - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Text = datetime.ToShortDateString(), + Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, }, new OsuSpriteText { Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, - Text = datetime.ToString("yyyy/MM/dd"), - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Text = datetime.ToShortTimeString(), + Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, } }; @@ -324,7 +325,14 @@ namespace osu.Game.Screens.Ranking title.Colour = artist.Colour = colours.BlueDarker; versionMapper.Colour = colours.Gray8; - versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.Metadata.Author.Username}"; + var creator = beatmap.Metadata.Author?.Username; + if (!string.IsNullOrEmpty(creator)) { + versionMapper.Text = $"mapped by {creator}"; + + if (!string.IsNullOrEmpty(beatmap.Version)) + versionMapper.Text = $"{beatmap.Version} - " + versionMapper.Text; + } + title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index be176c1459..b343998e11 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -53,6 +53,11 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; + /// + /// Used to avoid firing null selections before the initial beatmaps have been loaded via . + /// + private bool initialLoadComplete; + private IEnumerable beatmapSets => root.Children.OfType(); public IEnumerable BeatmapSets @@ -75,7 +80,12 @@ namespace osu.Game.Screens.Select scrollableContent.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); - BeatmapSetsChanged?.Invoke(); + + Schedule(() => + { + BeatmapSetsChanged?.Invoke(); + initialLoadComplete = true; + }); })); } } @@ -142,7 +152,6 @@ namespace osu.Game.Screens.Select if (newSet == null) { itemsCache.Invalidate(); - SelectNext(); return; } @@ -155,6 +164,7 @@ namespace osu.Game.Screens.Select select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); itemsCache.Invalidate(); + Schedule(() => BeatmapSetsChanged?.Invoke()); }); } @@ -184,7 +194,14 @@ namespace osu.Game.Screens.Select if (!Items.Any()) return; - int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.First()); + DrawableCarouselItem drawable = null; + + if (selectedBeatmap != null && (drawable = selectedBeatmap.Drawables.FirstOrDefault()) == null) + // if the selected beatmap isn't present yet, we can't correctly change selection. + // we can fix this by changing this method to not reference drawables / Items in the first place. + return; + + int originalIndex = Items.IndexOf(drawable); int currentIndex = originalIndex; // local function to increment the index in the required direction, wrapping over extremities. @@ -512,7 +529,7 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - if (selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected) + if (initialLoadComplete && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { selectedBeatmapSet = null; SelectionChanged?.Invoke(null); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a9a778fe17..79d76dd00e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Screens.Select.Details; using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select { @@ -334,7 +335,7 @@ namespace osu.Game.Screens.Select TextSize = 14, }, }, - textFlow = new TextFlowContainer + textFlow = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -359,7 +360,7 @@ namespace osu.Game.Screens.Select private void setTextAsync(string text) { - LoadComponentAsync(new TextFlowContainer(s => s.TextSize = 14) + LoadComponentAsync(new OsuTextFlowContainer(s => s.TextSize = 14) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3ef6ceeaeb..729cb458c2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Select { private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0); - private Drawable info; + protected BufferedWedgeInfo Info; public BeatmapInfoWedge() { @@ -35,6 +35,7 @@ namespace osu.Game.Screens.Select Masking = true; BorderColour = new Color4(221, 255, 255, 255); BorderThickness = 2.5f; + Alpha = 0; EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, @@ -50,12 +51,14 @@ namespace osu.Game.Screens.Select { this.MoveToX(0, 800, Easing.OutQuint); this.RotateTo(0, 800, Easing.OutQuint); + this.FadeIn(250); } protected override void PopOut() { - this.MoveToX(-100, 800, Easing.InQuint); - this.RotateTo(10, 800, Easing.InQuint); + this.MoveToX(-100, 800, Easing.In); + this.RotateTo(10, 800, Easing.In); + this.FadeOut(500, Easing.In); } public void UpdateBeatmap(WorkingBeatmap beatmap) @@ -63,23 +66,26 @@ namespace osu.Game.Screens.Select LoadComponentAsync(new BufferedWedgeInfo(beatmap) { Shear = -Shear, - Depth = info?.Depth + 1 ?? 0, + Depth = Info?.Depth + 1 ?? 0, }, newInfo => { - // ensure we ourselves are visible if not already. - if (!IsPresent) - this.FadeIn(250); + State = beatmap == null ? Visibility.Hidden : Visibility.Visible; - info?.FadeOut(250); - info?.Expire(); + Info?.FadeOut(250); + Info?.Expire(); - Add(info = newInfo); + Add(Info = newInfo); }); } public class BufferedWedgeInfo : BufferedContainer { private readonly WorkingBeatmap working; + public OsuSpriteText VersionLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } + public OsuSpriteText ArtistLabel { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } + public FillFlowContainer InfoLabelContainer { get; private set; } public BufferedWedgeInfo(WorkingBeatmap working) { @@ -89,34 +95,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - BeatmapInfo beatmapInfo = working.BeatmapInfo; - BeatmapMetadata metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Beatmap beatmap = working.Beatmap; - - List labels = new List(); - - if (beatmap != null) - { - HitObject lastObject = beatmap.HitObjects.LastOrDefault(); - double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "Length", - Icon = FontAwesome.fa_clock_o, - Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), - })); - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "BPM", - Icon = FontAwesome.fa_circle, - Content = getBPMRange(beatmap), - })); - - //get statistics from the current ruleset. - labels.AddRange(beatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s))); - } + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); PixelSnapping = true; CacheDrawnFrameBuffer = true; @@ -165,7 +145,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new OsuSpriteText + VersionLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", Text = beatmapInfo.Version, @@ -175,69 +155,112 @@ namespace osu.Game.Screens.Select }, new FillFlowContainer { - Name = "Bottom-aligned metadata", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Name = "Centre-aligned metadata", + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Y = -22, Direction = FillDirection.Vertical, Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new OsuSpriteText + TitleLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = !string.IsNullOrEmpty(metadata.Source) ? metadata.Source + " — " + metadata.Title : metadata.Title, + Text = string.IsNullOrEmpty(metadata.Source) ? metadata.Title : metadata.Source + " — " + metadata.Title, TextSize = 28, }, - new OsuSpriteText + ArtistLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", Text = metadata.Artist, TextSize = 17, }, - new FillFlowContainer + MapperContainer = new FillFlowContainer { Margin = new MarginPadding { Top = 10 }, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Children = new[] - { - new OsuSpriteText - { - Font = @"Exo2.0-Medium", - Text = "mapped by ", - TextSize = 15, - }, - new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = metadata.Author.Username, - TextSize = 15, - }, - } + Children = getMapper(metadata) }, - new FillFlowContainer + InfoLabelContainer = new FillFlowContainer { Margin = new MarginPadding { Top = 20 }, Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, - Children = labels - }, + Children = getInfoLabels() + } } - }, + } }; } + private InfoLabel[] getInfoLabels() + { + var beatmap = working.Beatmap; + var info = working.BeatmapInfo; + + List labels = new List(); + + if (beatmap?.HitObjects?.Count > 0) + { + HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "Length", + Icon = FontAwesome.fa_clock_o, + Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), + })); + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "BPM", + Icon = FontAwesome.fa_circle, + Content = getBPMRange(beatmap), + })); + + //get statistics from the current ruleset. + labels.AddRange(info.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s))); + } + + return labels.ToArray(); + } + private string getBPMRange(Beatmap beatmap) { double bpmMax = beatmap.ControlPointInfo.BPMMaximum; double bpmMin = beatmap.ControlPointInfo.BPMMinimum; - if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; + if (Precision.AlmostEquals(bpmMin, bpmMax)) + return $"{bpmMin:0}"; return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})"; } + private OsuSpriteText[] getMapper(BeatmapMetadata metadata) + { + if (string.IsNullOrEmpty(metadata.Author?.Username)) + return Array.Empty(); + + return new[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Medium", + Text = "mapped by ", + TextSize = 15, + }, + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = metadata.Author.Username, + TextSize = 15, + } + }; + } + public class InfoLabel : Container, IHasTooltip { public string TooltipText { get; private set; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 6c0cc341fd..cea658b06c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Select.Carousel { if (songSelect != null) { - startRequested = songSelect.Start; + startRequested = songSelect.FinaliseSelection; editRequested = songSelect.Edit; } diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index f02d25501e..37f2663d91 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -7,6 +7,10 @@ namespace osu.Game.Screens.Select { protected override bool ShowFooter => false; - protected override void Start() => Exit(); + protected override bool OnSelectionFinalised() + { + Exit(); + return true; + } } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 1b86cec613..2dbd63f77b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Select { Children = new Drawable[] { - new Box + Background = new Box { Colour = Color4.Black, Alpha = 0.8f, @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Select private Bindable showConverted; + public readonly Box Background; + [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, OsuConfigManager config) { diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs new file mode 100644 index 0000000000..03e9462636 --- /dev/null +++ b/osu.Game/Screens/Select/ImportFromStablePopup.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 System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Select +{ + public class ImportFromStablePopup : PopupDialog + { + public ImportFromStablePopup(Action importFromStable) + { + HeaderText = @"You have no beatmaps!"; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?"; + + Icon = FontAwesome.fa_plane; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes please!", + Action = importFromStable + }, + new PopupDialogCancelButton + { + Text = @"No, I'd like to start from scratch", + }, + }; + } + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 03466439ad..e0c9a3e04e 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Select.Leaderboards Masking = true, Children = new Drawable[] { - new DrawableFlag(Score.User?.Country?.FlagName) + new DrawableFlag(Score.User?.Country) { Width = 30, RelativeSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 898c195432..9143da326d 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -5,6 +5,10 @@ namespace osu.Game.Screens.Select { public class MatchSongSelect : SongSelect { - protected override void Start() => Exit(); + protected override bool OnSelectionFinalised() + { + Exit(); + return true; + } } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4a0ee31fbb..87b3485dc1 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; @@ -45,8 +46,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleConfirm; - [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay) { sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); @@ -59,6 +60,16 @@ namespace osu.Game.Screens.Select ValidForResume = false; Push(new Editor()); }, Key.Number3); + + if (dialogOverlay != null) + { + Schedule(() => + { + // if we have no beatmaps but osu-stable is found, let's prompt the user to import. + if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable) + dialogOverlay.Push(new ImportFromStablePopup(() => beatmaps.ImportFromStable())); + }); + } } protected override void UpdateBeatmap(WorkingBeatmap beatmap) @@ -113,9 +124,9 @@ namespace osu.Game.Screens.Select return false; } - protected override void Start() + protected override bool OnSelectionFinalised() { - if (player != null) return; + if (player != null) return false; // Ctrl+Enter should start map with autoplay enabled. if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) @@ -136,7 +147,12 @@ namespace osu.Game.Screens.Select sampleConfirm?.Play(); - LoadComponentAsync(player = new PlayerLoader(new Player()), l => Push(player)); + LoadComponentAsync(player = new PlayerLoader(new Player()), l => + { + if (IsCurrentScreen) Push(player); + }); + + return true; } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 68ee08e721..b89a8a4e73 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select protected Container LeftContent; - private readonly BeatmapCarousel carousel; + protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; private BeatmapManager beatmaps; @@ -103,25 +103,44 @@ namespace osu.Game.Screens.Select Right = left_area_padding * 2, } }, - carousel = new BeatmapCarousel + new Container { - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - SelectionChanged = carouselSelectionChanged, - BeatmapSetsChanged = carouselBeatmapsLoaded, - }, - FilterControl = new FilterControl - { - RelativeSizeAxes = Axes.X, - Height = filter_height, - FilterChanged = c => carousel.Filter(c), - Exit = Exit, + RelativeSizeAxes = Axes.Both, + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 2, //avoid horizontal masking so the panels don't clip when screen stack is pushed. + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + Children = new Drawable[] + { + Carousel = new BeatmapCarousel + { + Masking = false, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(carousel_width, 1), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + SelectionChanged = carouselSelectionChanged, + BeatmapSetsChanged = carouselBeatmapsLoaded, + }, + FilterControl = new FilterControl + { + RelativeSizeAxes = Axes.X, + Height = filter_height, + FilterChanged = c => Carousel.Filter(c), + Background = { Width = 2 }, + Exit = Exit, + }, + } + }, }, beatmapInfoWedge = new BeatmapInfoWedge { - Alpha = 0, Size = wedged_container_size, RelativeSizeAxes = Axes.X, Margin = new MarginPadding @@ -130,7 +149,7 @@ namespace osu.Game.Screens.Select Right = left_area_padding, }, }, - new ResetScrollContainer(() => carousel.ScrollToSelected()) + new ResetScrollContainer(() => Carousel.ScrollToSelected()) { RelativeSizeAxes = Axes.Y, Width = 250, @@ -190,15 +209,15 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); - carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); + Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); - Beatmap.DisabledChanged += disabled => carousel.AllowSelection = !disabled; + Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.TriggerChange(); Beatmap.ValueChanged += b => { if (IsCurrentScreen) - carousel.SelectBeatmap(b?.BeatmapInfo); + Carousel.SelectBeatmap(b?.BeatmapInfo); }; } @@ -208,13 +227,18 @@ namespace osu.Game.Screens.Select Push(new Editor()); } - public void Start(BeatmapInfo beatmap) + /// + /// Call to make a selection and perform the default action for this SongSelect. + /// + /// An optional beatmap to override the current carousel selection. + public void FinaliseSelection(BeatmapInfo beatmap = null) { // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). - carousel.FlushPendingFilterOperations(); + Carousel.FlushPendingFilterOperations(); - carousel.SelectBeatmap(beatmap); + if (beatmap != null) + Carousel.SelectBeatmap(beatmap); if (selectionChangedDebounce?.Completed == false) { @@ -223,13 +247,14 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - Start(); + OnSelectionFinalised(); } /// /// Called when a selection is made. /// - protected abstract void Start(); + /// If a resultant action occurred that takes the user away from SongSelect. + protected abstract bool OnSelectionFinalised(); private ScheduledDelegate selectionChangedDebounce; @@ -282,9 +307,9 @@ namespace osu.Game.Screens.Select private void triggerRandom() { if (GetContainingInputManager().CurrentState.Keyboard.ShiftPressed) - carousel.SelectPreviousRandom(); + Carousel.SelectPreviousRandom(); else - carousel.SelectNextRandom(); + Carousel.SelectNextRandom(); } protected override void OnEntering(Screen last) @@ -320,7 +345,7 @@ namespace osu.Game.Screens.Select logo.Action = () => { - Start(); + FinaliseSelection(); return false; }; } @@ -399,7 +424,6 @@ namespace osu.Game.Screens.Select backgroundModeBeatmap.FadeTo(1, 250); } - beatmapInfoWedge.State = Visibility.Visible; beatmapInfoWedge.UpdateBeatmap(beatmap); } @@ -417,17 +441,17 @@ namespace osu.Game.Screens.Select } } - private void onBeatmapSetAdded(BeatmapSetInfo s) => carousel.UpdateBeatmapSet(s); - private void onBeatmapSetRemoved(BeatmapSetInfo s) => carousel.RemoveBeatmapSet(s); - private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); + private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); + private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void carouselBeatmapsLoaded() { - if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) - carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); - else - carousel.SelectNextRandom(); + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false) + Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); + else if (Carousel.SelectedBeatmapSet == null) + Carousel.SelectNextRandom(); } private void delete(BeatmapSetInfo beatmap) @@ -444,7 +468,7 @@ namespace osu.Game.Screens.Select { case Key.KeypadEnter: case Key.Enter: - Start(); + FinaliseSelection(); return true; case Key.Delete: if (state.Keyboard.ShiftPressed) diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index 3e7ab56c99..fbf24eb609 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Tournament { private const string results_filename = "drawings_results.txt"; - public override bool ShowOverlays => false; + public override bool ShowOverlaysOnEnter => false; protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index da139775b1..82248c2cb8 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -19,6 +19,6 @@ namespace osu.Game.Tests.Beatmaps protected override Beatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override Track GetTrack() => null; + protected override Track GetTrack() => new TrackVirtual(); } } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index f71bece279..8984fc843f 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -12,7 +12,6 @@ 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; @@ -224,7 +223,7 @@ namespace osu.Game.Tests.Visual if (!api.IsLoggedIn) { - InternalChild = new SpriteText + InternalChild = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 106f0fa8f3..933781890f 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tests.Visual protected Player Player; + private TestWorkingBeatmap working; + /// /// Create a TestCase which runs through the Player screen. /// @@ -75,7 +77,7 @@ namespace osu.Game.Tests.Visual var instance = r.CreateInstance(); - WorkingBeatmap working = new TestWorkingBeatmap(beatmap); + working = new TestWorkingBeatmap(beatmap); working.Mods.Value = new[] { instance.GetAllMods().First(m => m is ModNoFail) }; if (Player != null) @@ -88,10 +90,21 @@ namespace osu.Game.Tests.Visual return player; } + protected override void Update() + { + base.Update(); + + if (working != null) + // note that this will override any mod rate application + working.Track.Rate = Clock.Rate; + } + protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) => new Player { InitialBeatmap = beatmap, - AllowPause = false + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, }; private const string test_beatmap_data = diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 0c0d12c1cc..46ddaee637 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -26,36 +27,30 @@ namespace osu.Game.Users public string FlagName; } - public class DrawableFlag : Container + public class DrawableFlag : Container, IHasTooltip { private readonly Sprite sprite; private TextureStore textures; - private string flagName; - public string FlagName + private Country country; + public Country Country { - get { return flagName; } + get { return country; } set { - if (value == flagName) return; - flagName = value; - sprite.Texture = textures.Get($@"Flags/{flagName}"); + if (value == country) + return; + + country = value; + sprite.Texture = getFlagTexture(); } } - [BackgroundDependencyLoader] - private void load(TextureStore ts) - { - if (ts == null) - throw new ArgumentNullException(nameof(ts)); + public string TooltipText => country?.FullName; - textures = ts; - sprite.Texture = textures.Get($@"Flags/{flagName}"); - } - - public DrawableFlag(string name = null) + public DrawableFlag(Country country = null) { - flagName = name ?? @"__"; + this.country = country; Children = new Drawable[] { @@ -65,5 +60,17 @@ namespace osu.Game.Users }, }; } + + [BackgroundDependencyLoader] + private void load(TextureStore ts) + { + if (ts == null) + throw new ArgumentNullException(nameof(ts)); + + textures = ts; + sprite.Texture = getFlagTexture(); + } + + private Texture getFlagTexture() => textures.Get($@"Flags/{country?.FlagName ?? @"__"}"); } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d056afcf54..e0a4e3184d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,8 +16,8 @@ using osu.Game.Overlays; 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; +using osu.Game.Overlays.Profile; namespace osu.Game.Users { @@ -114,7 +114,7 @@ namespace osu.Game.Users Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new DrawableFlag(user.Country?.FlagName) + new DrawableFlag(user.Country) { Width = 30f, RelativeSizeAxes = Axes.Y, @@ -220,53 +220,5 @@ namespace osu.Game.Users { new OsuMenuItem("View Profile", MenuItemType.Highlighted, ViewProfile), }; - - private class SupporterIcon : CircularContainer - { - private readonly Box background; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 94678106bf..82b9f41567 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -143,8 +143,8 @@ $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + + $(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll True @@ -239,7 +239,6 @@ - @@ -268,6 +267,7 @@ + @@ -310,6 +310,8 @@ + + @@ -610,7 +612,7 @@ - + diff --git a/osu.Game/packages.config b/osu.Game/packages.config index 02ace918de..e35f06dd67 100644 --- a/osu.Game/packages.config +++ b/osu.Game/packages.config @@ -66,7 +66,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 2f52881d6d..20007e3306 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -172,6 +172,7 @@ NEXT_LINE NEXT_LINE True + NEVER False False True @@ -655,7 +656,11 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True + True + True + True True True + True True True