1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 11:12:54 +08:00

Merge remote-tracking branch 'refs/remotes/ppy/master'

This commit is contained in:
Shawdooow 2017-12-31 10:38:39 -05:00
commit dc365bfc11
171 changed files with 2081 additions and 933 deletions

36
COMPILING.md Normal file
View File

@ -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.

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="opentk-develop" value="https://www.myget.org/F/opentk-develop" />
</packageSources>
</configuration>

View File

@ -8,8 +8,11 @@ This is still heavily under development and is not intended for end-user use. Th
# Requirements # 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. - 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.
- Make sure you initialise and keep submodules up-to-date.
# 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 # Contributing

@ -1 +1 @@
Subproject commit 08f85f9bf9a7376aec8dfcde8c7c96d267d8c295 Subproject commit 6134dafccb3368dac96d837537325c04b89fb8ee

@ -1 +1 @@
Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f Subproject commit e01f71160fb9b3167efcd177c7d7dba9e5d36604

View File

@ -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). // only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion)) if (!string.IsNullOrEmpty(lastVersion))
Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000); notificationOverlay.Post(new UpdateCompleteNotification(version));
} }
} }

View File

@ -135,8 +135,8 @@
<HintPath>$(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll</HintPath> <HintPath>$(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SharpCompress, Version=0.18.1.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL"> <Reference Include="SharpCompress, Version=0.18.1.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">

View File

@ -6,7 +6,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
<packages> <packages>
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net45" /> <package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net45" />
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" /> <package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
<package id="SharpCompress" version="0.18.1" targetFramework="net461" /> <package id="SharpCompress" version="0.18.1" targetFramework="net461" />
<package id="Splat" version="2.0.0" targetFramework="net45" /> <package id="Splat" version="2.0.0" targetFramework="net45" />
<package id="SQLitePCLRaw.bundle_green" version="1.1.8" targetFramework="net461" /> <package id="SQLitePCLRaw.bundle_green" version="1.1.8" targetFramework="net461" />

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Objects
StartTime = lastTickTime, StartTime = lastTickTime,
ComboColour = ComboColour, ComboColour = ComboColour,
X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Catch.Objects
StartTime = repeatStartTime + t, StartTime = repeatStartTime + t,
ComboColour = ComboColour, ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.Objects
set { Curve.ControlPoints = value; } set { Curve.ControlPoints = value; }
} }
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>(); public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public CurveType CurveType public CurveType CurveType
{ {

View File

@ -5,7 +5,6 @@ using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;

View File

@ -36,8 +36,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private SampleInfoList sampleInfoListAt(double time) private List<SampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private SampleInfoList sampleInfoListAt(double time) private List<SampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}; };
if (hold.Head.Samples == null) if (hold.Head.Samples == null)
hold.Head.Samples = new SampleInfoList(); hold.Head.Samples = new List<SampleInfo>();
hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL });

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // 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 namespace osu.Game.Rulesets.Mania.Judgements
{ {
@ -24,4 +24,4 @@ namespace osu.Game.Rulesets.Mania.Judgements
} }
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // 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 namespace osu.Game.Rulesets.Mania.Judgements
{ {
@ -11,4 +11,4 @@ namespace osu.Game.Rulesets.Mania.Judgements
protected override int NumericResultFor(HitResult result) => 20; protected override int NumericResultFor(HitResult result) => 20;
} }
} }

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
@ -113,4 +114,4 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
UpdateJudgement(true); UpdateJudgement(true);
} }
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {

View File

@ -6,7 +6,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI; 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.Rulesets.Timing;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;

View File

@ -36,8 +36,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -4,7 +4,7 @@
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Judgements namespace osu.Game.Rulesets.Osu.Judgements
{ {
@ -34,4 +34,4 @@ namespace osu.Game.Rulesets.Osu.Judgements
public ComboResult Combo; public ComboResult Combo;
} }
} }

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK; 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 namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy 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 string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06; 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<DrawableHitObject> drawables)
{
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
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<OsuHitObject> public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints; slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve slider.Curve?.Calculate(); // Recalculate the slider curve
} }
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
} }
public class OsuModSuddenDeath : ModSuddenDeath public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect public class OsuModPerfect : ModPerfect
{ {
} }
public class OsuModSpunOut : Mod public class OsuModSpunOut : Mod

View File

@ -6,8 +6,8 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number; private readonly NumberPiece number;
private readonly GlowPiece glow; private readonly GlowPiece glow;
public DrawableHitCircle(OsuHitObject h) : base(h) public DrawableHitCircle(HitCircle h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
number = new NumberPiece number = new NumberPiece
{ {
Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(), Text = (HitObject.ComboIndex + 1).ToString(),
}, },
ring = new RingPiece(), ring = new RingPiece(),
flash = new FlashPiece(), flash = new FlashPiece(),
@ -88,25 +88,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdatePreemptState(); 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); ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime; glow.FadeOut(400);
glow.Delay(duration).FadeOut(400);
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT); this.Delay(TIME_PREEMPT).FadeOut(500);
Expire(true); 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; break;
case ArmedState.Miss: case ArmedState.Miss:
ApproachCircle.FadeOut(50); ApproachCircle.FadeOut(50);
this.FadeOut(TIME_FADEOUT / 5); this.FadeOut(100);
Expire(); Expire();
break; break;
case ArmedState.Hit: case ArmedState.Hit:

View File

@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public const float TIME_PREEMPT = 600; public const float TIME_PREEMPT = 600;
public const float TIME_FADEIN = 400; public const float TIME_FADEIN = 400;
public const float TIME_FADEOUT = 500;
/// <summary>
/// The number of milliseconds used to fade in.
/// </summary>
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) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected virtual void UpdatePreemptState() protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration);
{
this.FadeIn(TIME_FADEIN);
}
protected virtual void UpdateCurrentState(ArmedState state) protected virtual void UpdateCurrentState(ArmedState state)
{ {

View File

@ -2,10 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -24,4 +24,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.LoadComplete(); base.LoadComplete();
} }
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -24,19 +25,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.repeatPoint = repeatPoint; this.repeatPoint = repeatPoint;
this.drawableSlider = drawableSlider; this.drawableSlider = drawableSlider;
AutoSizeAxes = Axes.Both; Size = new Vector2(32 * repeatPoint.Scale);
Blending = BlendingMode.Additive; Blending = BlendingMode.Additive;
Origin = Anchor.Centre; Origin = Anchor.Centre;
Scale = new Vector2(0.5f);
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteIcon new SpriteIcon
{ {
Icon = FontAwesome.fa_eercast, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Icon = FontAwesome.fa_eercast
Origin = Anchor.Centre,
Size = new Vector2(32),
} }
}; };
} }

View File

@ -10,6 +10,7 @@ using System.Linq;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -17,23 +18,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
private readonly Slider slider; private readonly Slider slider;
private readonly DrawableHitCircle initialCircle; public readonly DrawableHitCircle InitialCircle;
private readonly List<ISliderProgress> components = new List<ISliderProgress>(); private readonly List<ISliderProgress> components = new List<ISliderProgress>();
private readonly Container<DrawableSliderTick> ticks; private readonly Container<DrawableSliderTick> ticks;
private readonly Container<DrawableRepeatPoint> repeatPoints; private readonly Container<DrawableRepeatPoint> repeatPoints;
private readonly SliderBody body; public readonly SliderBody Body;
private readonly SliderBall ball; public readonly SliderBall Ball;
public DrawableSlider(Slider s) : base(s) public DrawableSlider(Slider s)
: base(s)
{ {
slider = s; slider = s;
Children = new Drawable[] Children = new Drawable[]
{ {
body = new SliderBody(s) Body = new SliderBody(s)
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Position = s.StackedPosition, Position = s.StackedPosition,
@ -41,16 +43,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
ticks = new Container<DrawableSliderTick>(), ticks = new Container<DrawableSliderTick>(),
repeatPoints = new Container<DrawableRepeatPoint>(), repeatPoints = new Container<DrawableRepeatPoint>(),
ball = new SliderBall(s) Ball = new SliderBall(s)
{ {
Scale = new Vector2(s.Scale), Scale = new Vector2(s.Scale),
AccentColour = AccentColour, AccentColour = AccentColour,
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
initialCircle = new DrawableHitCircle(new HitCircle InitialCircle = new DrawableHitCircle(new HitCircle
{ {
//todo: avoid creating this temporary HitCircle.
StartTime = s.StartTime, StartTime = s.StartTime,
Position = s.StackedPosition, Position = s.StackedPosition,
ComboIndex = s.ComboIndex, ComboIndex = s.ComboIndex,
@ -61,16 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}) })
}; };
components.Add(body); components.Add(Body);
components.Add(ball); components.Add(Ball);
AddNested(initialCircle); AddNested(InitialCircle);
var repeatDuration = s.Curve.Distance / s.Velocity; var repeatDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>()) foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{ {
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; 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 fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick) var drawableTick = new DrawableSliderTick(tick)
@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>()) foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{ {
var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; 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 fadeOutTime = repeatStartTime + repeatDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
@ -105,11 +106,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private int currentRepeat; private int currentRepeat;
public bool Tracking; public bool Tracking;
public override double FadeInDuration
{
get { return base.FadeInDuration; }
set { InitialCircle.FadeInDuration = base.FadeInDuration = value; }
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
Tracking = ball.Tracking; Tracking = Ball.Tracking;
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); 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); progress = slider.ProgressAt(progress);
if (repeat > currentRepeat) if (repeat > currentRepeat)
{
if (repeat < slider.RepeatCount && ball.Tracking)
PlaySamples();
currentRepeat = repeat; currentRepeat = repeat;
}
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!initialCircle.Judgements.Any(j => j.IsHit)) if (!InitialCircle.Judgements.Any(j => j.IsHit))
initialCircle.Position = slider.Curve.PositionAt(progress); InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components) c.UpdateProgress(progress, repeat); 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) 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 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)); 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++; judgementsHit++;
var hitFraction = (double)judgementsHit / judgementsCount; 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 }); 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 }); AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (hitFraction > 0) else if (hitFraction > 0)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); AddJudgement(new OsuJudgement { Result = HitResult.Meh });
@ -154,26 +157,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
ball.FadeIn(); Ball.FadeIn();
using (BeginDelayedSequence(slider.Duration, true)) using (BeginDelayedSequence(slider.Duration, true))
{ {
body.FadeOut(160); Body.FadeOut(160);
ball.FadeOut(160); Ball.FadeOut(160);
this.FadeOut(800) this.FadeOut(800)
.Expire(); .Expire();
} }
} }
public Drawable ProxiedLayer => initialCircle.ApproachCircle; public Drawable ProxiedLayer => InitialCircle.ApproachCircle;
public override Vector2 SelectionPoint => ToScreenSpace(body.Position); public override Vector2 SelectionPoint => ToScreenSpace(Body.Position);
public override Quad SelectionQuad => body.PathDrawQuad; public override Quad SelectionQuad => Body.PathDrawQuad;
}
internal interface ISliderProgress
{
void UpdateProgress(double progress, int repeat);
} }
} }

View File

@ -8,6 +8,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {

View File

@ -13,6 +13,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -20,13 +21,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
private readonly Spinner spinner; private readonly Spinner spinner;
private readonly SpinnerDisc disc; public readonly SpinnerDisc Disc;
private readonly SpinnerTicks ticks; public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter; private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer; private readonly Container mainContainer;
private readonly SpinnerBackground background; public readonly SpinnerBackground Background;
private readonly Container circleContainer; private readonly Container circleContainer;
private readonly CirclePiece circle; private readonly CirclePiece circle;
private readonly GlowPiece glow; private readonly GlowPiece glow;
@ -84,20 +85,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
background = new SpinnerBackground Background = new SpinnerBackground
{ {
Alpha = 0.6f, Alpha = 0.6f,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
disc = new SpinnerDisc(spinner) Disc = new SpinnerDisc(spinner)
{ {
Scale = Vector2.Zero, Scale = Vector2.Zero,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
circleContainer.CreateProxy(), circleContainer.CreateProxy(),
ticks = new SpinnerTicks Ticks = new SpinnerTicks
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = 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) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (Time.Current < HitObject.StartTime) return; 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; const float duration = 200;
disc.FadeAccent(completeColour, duration); Disc.FadeAccent(completeColour, duration);
background.FadeAccent(completeColour, duration); Background.FadeAccent(completeColour, duration);
background.FadeOut(duration); Background.FadeOut(duration);
circle.FadeColour(completeColour, duration); circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration);
@ -153,20 +154,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
normalColour = baseColour; normalColour = baseColour;
background.AccentColour = normalColour; Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f); completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = fillColour; Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark; circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark; glow.Colour = colours.BlueDark;
} }
protected override void Update() protected override void Update()
{ {
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && disc.Tracking) if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(TIME_FADEIN); spmCounter.FadeIn(FadeInDuration);
base.Update(); base.Update();
} }
@ -175,14 +176,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
circle.Rotation = disc.Rotation; circle.Rotation = Disc.Rotation;
ticks.Rotation = disc.Rotation; Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute); spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; 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() 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 * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
disc.RotateTo(-720); Disc.RotateTo(-720);
symbol.RotateTo(-720); symbol.RotateTo(-720);
mainContainer mainContainer

View File

@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private const float idle_alpha = 0.2f; private const float idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f; private const float tracking_alpha = 0.4f;
public override bool IsPresent => true; // handle input when hidden
public SpinnerDisc(Spinner s) public SpinnerDisc(Spinner s)
{ {
spinner = s; spinner = s;

View File

@ -0,0 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}

View File

@ -7,7 +7,7 @@ using OpenTK;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
internal float LazyTravelDistance; internal float LazyTravelDistance;
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>(); public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } = 1; public int RepeatCount { get; set; } = 1;
private int stackHeight; private int stackHeight;
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Objects
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
ComboColour = ComboColour, ComboColour = ComboColour,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
@ -151,28 +151,22 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createRepeatPoints() private void createRepeatPoints()
{ {
var length = Curve.Distance; var repeatDuration = Distance / Velocity;
var repeatPointDistance = Math.Min(Distance, length);
var repeatDuration = length / Velocity;
for (var repeat = 1; repeat < RepeatCount; repeat++) for (var repeat = 1; repeat < RepeatCount; repeat++)
{ {
for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) var repeatStartTime = StartTime + repeat * repeatDuration;
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var distanceProgress = d / length;
AddNested(new RepeatPoint AddNested(new RepeatPoint
{ {
RepeatIndex = repeat, RepeatIndex = repeat,
StartTime = repeatStartTime, StartTime = repeatStartTime,
Position = Curve.PositionAt(distanceProgress), Position = Curve.PositionAt(repeat % 2),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
ComboColour = ComboColour, ComboColour = ComboColour,
Samples = new SampleInfoList(RepeatSamples[repeat]) Samples = new List<SampleInfo>(RepeatSamples[repeat])
}); });
}
} }
} }
} }

View File

@ -9,9 +9,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {

View File

@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Scoring
mods = Score.Mods; mods = Score.Mods;
accuracy = Score.Accuracy; accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo; scoreMaxCombo = Score.MaxCombo;
count300 = Convert.ToInt32(Score.Statistics["300"]); count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
count100 = Convert.ToInt32(Score.Statistics["100"]); count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
count50 = Convert.ToInt32(Score.Statistics["50"]); count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics["x"]); countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
// Don't count scores made with supposedly unranked mods // Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked)) if (mods.Any(m => !m.Ranked))

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -33,8 +32,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
var slider = obj as Slider; if (obj is Slider slider)
if (slider != null)
{ {
// Head // Head
AddJudgement(new OsuJudgement { Result = HitResult.Great }); AddJudgement(new OsuJudgement { Result = HitResult.Great });
@ -64,10 +62,10 @@ namespace osu.Game.Rulesets.Osu.Scoring
{ {
base.PopulateScore(score); base.PopulateScore(score);
score.Statistics[@"300"] = scoreResultCounts.GetOrDefault(HitResult.Great); score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great);
score.Statistics[@"100"] = scoreResultCounts.GetOrDefault(HitResult.Good); score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good);
score.Statistics[@"50"] = scoreResultCounts.GetOrDefault(HitResult.Meh); score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh);
score.Statistics[@"x"] = scoreResultCounts.GetOrDefault(HitResult.Miss); score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss);
} }
protected override void OnNewJudgement(Judgement judgement) protected override void OnNewJudgement(Judgement judgement)

View File

@ -0,0 +1,116 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> RequiredTypes => new[]
{
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableHitCircle)
};
private readonly Container content;
protected override Container<Drawable> 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);
}
}
}
}

View File

@ -1,129 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Vector2>
{
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
}
}
}

View File

@ -0,0 +1,146 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> RequiredTypes => new[]
{
typeof(Slider),
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableSlider),
typeof(DrawableHitCircle),
typeof(DrawableSliderTick),
typeof(DrawableRepeatPoint)
};
private readonly Container content;
protected override Container<Drawable> 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<Vector2>
{
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<List<SampleInfo>>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
var slider = new Slider
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
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);
}
}
}

View File

@ -0,0 +1,66 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> RequiredTypes => new[]
{
typeof(Spinner),
typeof(OsuModHidden),
typeof(DrawableSpinner)
};
private readonly Container content;
protected override Container<Drawable> 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);
}
}
}

View File

@ -35,16 +35,13 @@ namespace osu.Game.Rulesets.Osu.UI
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h) protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
{ {
var circle = h as HitCircle; if (h is HitCircle circle)
if (circle != null)
return new DrawableHitCircle(circle); return new DrawableHitCircle(circle);
var slider = h as Slider; if (h is Slider slider)
if (slider != null)
return new DrawableSlider(slider); return new DrawableSlider(slider);
var spinner = h as Spinner; if (h is Spinner spinner)
if (spinner != null)
return new DrawableSpinner(spinner); return new DrawableSpinner(spinner);
return null; return null;
} }

View File

@ -37,8 +37,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -75,6 +75,7 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\ISliderProgress.cs" />
<Compile Include="Objects\RepeatPoint.cs" /> <Compile Include="Objects\RepeatPoint.cs" />
<Compile Include="Objects\SliderTick.cs" /> <Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" /> <Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" />
@ -86,8 +87,10 @@
<Compile Include="OsuDifficulty\Utils\History.cs" /> <Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" /> <Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" /> <Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\TestCaseHitObjects.cs" /> <Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" /> <Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseSlider.cs" />
<Compile Include="Tests\TestCaseSpinner.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" /> <Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" /> <Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" /> <Compile Include="UI\OsuSettings.cs" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -0,0 +1,46 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio)
{
this.controlPoints = controlPoints;
IEnumerable<SampleControlPoint> 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;
}
}
}

View File

@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve; var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information // Old osu! used hit sounding to determine various hit type information
SampleInfoList samples = obj.Samples; List<SampleInfo> samples = obj.Samples;
bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); 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) if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{ {
List<SampleInfoList> allSamples = curveData != null ? curveData.RepeatSamples : new List<SampleInfoList>(new[] { samples }); List<List<SampleInfo>> allSamples = curveData != null ? curveData.RepeatSamples : new List<List<SampleInfo>>(new[] { samples });
int i = 0; int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{ {
SampleInfoList currentSamples = allSamples[i]; List<SampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // 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 namespace osu.Game.Rulesets.Taiko.Judgements
{ {
@ -20,4 +20,4 @@ namespace osu.Game.Rulesets.Taiko.Judgements
} }
} }
} }
} }

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements namespace osu.Game.Rulesets.Taiko.Judgements
{ {

View File

@ -13,6 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
validKeyPressed = HitActions.Contains(action); validKeyPressed = HitActions.Contains(action);
// Only count this as handled if the new judgement is a hit // 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() protected override void Update()

View File

@ -3,7 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset > second_hit_window) if (timeOffset > second_hit_window)
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Miss }); AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None });
return; return;
} }
@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return false; return false;
// Assume the intention was to hit the strong hit with both keys only if the first key is still being held down // 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);
} }
} }
} }

View File

@ -14,6 +14,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables 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 targetRing;
private readonly CircularContainer expandingRing; private readonly CircularContainer expandingRing;
private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim };
private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
private TaikoAction[] lastAction;
/// <summary> /// <summary>
/// The amount of times the user has hit this swell. /// The amount of times the user has hit this swell.
/// </summary> /// </summary>
@ -205,19 +202,20 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
} }
private bool? lastWasCentre;
public override bool OnPressed(TaikoAction action) public override bool OnPressed(TaikoAction action)
{ {
// Don't handle keys before the swell starts // Don't handle keys before the swell starts
if (Time.Current < HitObject.StartTime) if (Time.Current < HitObject.StartTime)
return false; return false;
// Find the keyset which this key corresponds to var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre;
var keySet = rimActions.Contains(action) ? rimActions : centreActions;
// Ensure alternating keysets // Ensure alternating centre and rim hits
if (keySet == lastAction) if (lastWasCentre == isCentre)
return false; return false;
lastAction = keySet; lastWasCentre = isCentre;
UpdateJudgement(true); UpdateJudgement(true);

View File

@ -6,6 +6,9 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK; using OpenTK;
using System.Linq;
using osu.Game.Audio;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
@ -35,6 +38,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
MainPiece.KiaiMode = HitObject.Kiai; MainPiece.KiaiMode = HitObject.Kiai;
} }
// Normal and clap samples are handled by the drum
protected override IEnumerable<SampleInfo> 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(); protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
public abstract bool OnPressed(TaikoAction action); public abstract bool OnPressed(TaikoAction action);

View File

@ -3,8 +3,6 @@
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System; using System;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -74,13 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
FirstTick = first, FirstTick = first,
TickSpacing = tickSpacing, TickSpacing = tickSpacing,
StartTime = t, StartTime = t,
IsStrong = IsStrong, IsStrong = IsStrong
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
}); });
first = false; first = false;

View File

@ -16,4 +16,4 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary> /// </summary>
public int RequiredHits = 10; public int RequiredHits = 10;
} }
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
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; hitButton = !hitButton;
} }
} }

View File

@ -4,7 +4,6 @@
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> 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())
}
});
}
}
}

View File

@ -20,6 +20,7 @@ using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
@ -165,11 +166,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addSwell(double duration = default_duration) 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, StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
Duration = duration, Duration = duration,
})); };
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
rulesetContainer.Playfield.Add(new DrawableSwell(swell));
} }
private void addDrumRoll(bool strong, double duration = default_duration) private void addDrumRoll(bool strong, double duration = default_duration)
@ -184,6 +189,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = duration, Duration = duration,
}; };
d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); rulesetContainer.Playfield.Add(new DrawableDrumRoll(d));
} }
@ -195,6 +202,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
IsStrong = strong IsStrong = strong
}; };
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong) if (strong)
rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h));
else else
@ -209,6 +218,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
IsStrong = strong IsStrong = strong
}; };
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong) if (strong)
rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h));
else else

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
@ -49,4 +50,4 @@ namespace osu.Game.Rulesets.Taiko.UI
base.LoadComplete(); base.LoadComplete();
} }
} }
} }

View File

@ -4,12 +4,15 @@
using System; using System;
using OpenTK; using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Audio;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
@ -18,16 +21,26 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary> /// </summary>
internal class InputDrum : Container 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; RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
}
const float middle_split = 0.025f; [BackgroundDependencyLoader]
private void load(AudioManager audio)
{
var sampleMappings = new DrumSampleMapping(controlPoints, audio);
Children = new Drawable[] Children = new Drawable[]
{ {
new TaikoHalfDrum(false) new TaikoHalfDrum(false, sampleMappings)
{ {
Name = "Left Half", Name = "Left Half",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -38,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RimAction = TaikoAction.LeftRim, RimAction = TaikoAction.LeftRim,
CentreAction = TaikoAction.LeftCentre CentreAction = TaikoAction.LeftCentre
}, },
new TaikoHalfDrum(true) new TaikoHalfDrum(true, sampleMappings)
{ {
Name = "Right Half", Name = "Right Half",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -72,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Sprite centre; private readonly Sprite centre;
private readonly Sprite centreHit; private readonly Sprite centreHit;
public TaikoHalfDrum(bool flipped) private readonly DrumSampleMapping sampleMappings;
public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings)
{ {
this.sampleMappings = sampleMappings;
Masking = true; Masking = true;
Children = new Drawable[] Children = new Drawable[]
@ -128,15 +145,21 @@ namespace osu.Game.Rulesets.Taiko.UI
Drawable target = null; Drawable target = null;
Drawable back = null; Drawable back = null;
var drumSample = sampleMappings.SampleAt(Time.Current);
if (action == CentreAction) if (action == CentreAction)
{ {
target = centreHit; target = centreHit;
back = centre; back = centre;
drumSample.Centre?.Play();
} }
else if (action == RimAction) else if (action == RimAction)
{ {
target = rimHit; target = rimHit;
back = rim; back = rim;
drumSample.Rim?.Play();
} }
if (target != null) if (target != null)

View File

@ -16,17 +16,11 @@ using osu.Framework.Extensions.Color4Extensions;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints; 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 namespace osu.Game.Rulesets.Taiko.UI
{ {
public class TaikoPlayfield : ScrollingPlayfield, IKeyBindingHandler<TaikoAction> public class TaikoPlayfield : ScrollingPlayfield
{ {
/// <summary> /// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="TaikoRulesetContainer"/>. /// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="TaikoRulesetContainer"/>.
@ -61,13 +55,9 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Box overlayBackground; private readonly Box overlayBackground;
private readonly Box background; private readonly Box background;
private readonly ControlPointInfo controlPointInfo; public TaikoPlayfield(ControlPointInfo controlPoints)
private Dictionary<SampleControlPoint, DrumSamples> drumSampleMappings;
public TaikoPlayfield(ControlPointInfo controlPointInfo)
: base(Axes.X) : base(Axes.X)
{ {
this.controlPointInfo = controlPointInfo;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
backgroundContainer = new Container backgroundContainer = new Container
@ -160,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
new InputDrum new InputDrum(controlPoints)
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
@ -205,19 +195,8 @@ namespace osu.Game.Rulesets.Taiko.UI
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio) private void load(OsuColour colours)
{ {
drumSampleMappings = new Dictionary<SampleControlPoint, DrumSamples>();
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; overlayBackgroundContainer.BorderColour = colours.Gray0;
overlayBackground.Colour = colours.Gray1; overlayBackground.Colour = colours.Gray1;
@ -282,28 +261,5 @@ namespace osu.Game.Rulesets.Taiko.UI
kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim)); 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;
}
} }
} }

View File

@ -36,14 +36,15 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Audio\DrumSampleMapping.cs" />
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" /> <Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
<Compile Include="Judgements\TaikoDrumRollTickJudgement.cs" /> <Compile Include="Judgements\TaikoDrumRollTickJudgement.cs" />
<Compile Include="Judgements\TaikoStrongHitJudgement.cs" /> <Compile Include="Judgements\TaikoStrongHitJudgement.cs" />
@ -82,6 +83,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" /> <Compile Include="Scoring\TaikoScoreProcessor.cs" />
<Compile Include="TaikoInputManager.cs" /> <Compile Include="TaikoInputManager.cs" />
<Compile Include="Tests\TestCaseInputDrum.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" /> <Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" /> <Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
<Compile Include="UI\HitTarget.cs" /> <Compile Include="UI\HitTarget.cs" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual
testRemoveAll(); testRemoveAll();
testEmptyTraversal(); testEmptyTraversal();
testHiding();
} }
private void ensureRandomFetchSuccess() => private void ensureRandomFetchSuccess() =>
@ -295,6 +296,40 @@ namespace osu.Game.Tests.Visual
checkNoSelection(); 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) private BeatmapSetInfo createTestBeatmapSet(int i)
{ {
return new BeatmapSetInfo return new BeatmapSetInfo

View File

@ -1,69 +1,161 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // 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 OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; 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.Screens.Select;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
public class TestCaseBeatmapInfoWedge : OsuTestCase public class TestCaseBeatmapInfoWedge : OsuTestCase
{ {
private BeatmapManager beatmaps; private RulesetStore rulesets;
private readonly Random random; private TestBeatmapInfoWedge infoWedge;
private readonly BeatmapInfoWedge infoWedge; private readonly List<Beatmap> beatmaps = new List<Beatmap>();
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>(); private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
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), Size = new Vector2(0.5f, 245),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Margin = new MarginPadding Margin = new MarginPadding { Top = 20 }
{
Top = 20,
},
}); });
AddStep("show", () => AddStep("show", () =>
{ {
Content.FadeInFromZero(250);
infoWedge.State = Visibility.Visible; infoWedge.State = Visibility.Visible;
infoWedge.UpdateBeatmap(beatmap); 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; var ruleset = rulesetInfo.CreateInstance();
Content.FadeOut(100); 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<OsuSpriteText>().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 selectNullBeatmap()
private void load(OsuGameBase game, BeatmapManager beatmaps)
{ {
this.beatmaps = beatmaps; AddStep("select null beatmap", () =>
beatmap.BindTo(game.Beatmap); {
beatmap.Value = beatmap.Default;
infoWedge.UpdateBeatmap(beatmap);
});
} }
private void randomBeatmap() private Beatmap createTestBeatmap(RulesetInfo ruleset)
{ {
var sets = beatmaps.GetAllUsableBeatmapSets(); List<HitObject> objects = new List<HitObject>();
if (sets.Count == 0) for (double i = 0; i < 50000; i += 1000)
return; objects.Add(new HitObject { StartTime = i });
var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; return new Beatmap
beatmap.Value = beatmaps.GetWorkingBeatmap(b); {
infoWedge.UpdateBeatmap(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;
} }
} }
} }

View File

@ -160,9 +160,9 @@ namespace osu.Game.Tests.Visual
}; };
foreach(var s in scores) foreach(var s in scores)
{ {
s.Statistics.Add("300", RNG.Next(2000)); s.Statistics.Add(HitResult.Great, RNG.Next(2000));
s.Statistics.Add("100", RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000));
s.Statistics.Add("50", RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
} }
anotherScores = new[] anotherScores = new[]
@ -272,9 +272,9 @@ namespace osu.Game.Tests.Visual
}; };
foreach (var s in anotherScores) foreach (var s in anotherScores)
{ {
s.Statistics.Add("300", RNG.Next(2000)); s.Statistics.Add(HitResult.Great, RNG.Next(2000));
s.Statistics.Add("100", RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000));
s.Statistics.Add("50", RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
} }
topScore = new OnlineScore topScore = new OnlineScore
@ -299,9 +299,9 @@ namespace osu.Game.Tests.Visual
TotalScore = 987654321, TotalScore = 987654321,
Accuracy = 0.8487, Accuracy = 0.8487,
}; };
topScore.Statistics.Add("300", RNG.Next(2000)); topScore.Statistics.Add(HitResult.Great, RNG.Next(2000));
topScore.Statistics.Add("100", RNG.Next(2000)); topScore.Statistics.Add(HitResult.Good, RNG.Next(2000));
topScore.Statistics.Add("50", RNG.Next(2000)); topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000));
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual
{ {
AddStep("Show overlay", () => failOverlay.Show()); 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()); AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); 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(); var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); 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("First button not selected", () => !pauseOverlay.Buttons.First().Selected);
AddAssert("Second button selected", () => secondButton.Selected); AddAssert("Second button selected", () => secondButton.Selected);
@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual
var secondButton = pauseOverlay.Buttons.Skip(1).First(); 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 })); AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Second button not selected", () => !secondButton.Selected); AddAssert("Second button not selected", () => !secondButton.Selected);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().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(); 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("Unhover second button", () => secondButton.TriggerOnHoverLost(null));
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition

View File

@ -1,21 +1,32 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture]
public class TestCaseNotificationOverlay : OsuTestCase public class TestCaseNotificationOverlay : OsuTestCase
{ {
private readonly NotificationOverlay manager; private readonly NotificationOverlay manager;
private readonly List<ProgressNotification> progressingNotifications = new List<ProgressNotification>();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(NotificationSection),
typeof(SimpleNotification),
typeof(ProgressNotification),
typeof(ProgressCompletionNotification),
typeof(IHasCompletionTarget),
typeof(Notification)
};
public TestCaseNotificationOverlay() public TestCaseNotificationOverlay()
{ {
@ -24,33 +35,65 @@ namespace osu.Game.Tests.Visual
Content.Add(manager = new NotificationOverlay Content.Add(manager = new NotificationOverlay
{ {
Anchor = Anchor.TopRight, 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); Content.Add(displayedCount);
AddStep(@"simple #2", sendNotification2);
AddStep(@"progress #1", sendProgress1); void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state);
AddStep(@"progress #2", sendProgress2); void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
AddStep(@"barrage", () => sendBarrage());
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)) switch (RNG.Next(0, 4))
{ {
case 0: case 0:
sendNotification1(); sendHelloNotification();
break; break;
case 1: case 1:
sendNotification2(); sendAmazingNotification();
break; break;
case 2: case 2:
sendProgress1(); sendUploadProgress();
break; break;
case 3: case 3:
sendProgress2(); sendDownloadProgress();
break; break;
} }
@ -66,7 +109,7 @@ namespace osu.Game.Tests.Visual
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) 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) if (p != null)
p.State = ProgressNotificationState.Active; p.State = ProgressNotificationState.Active;
} }
@ -74,13 +117,13 @@ namespace osu.Game.Tests.Visual
foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
{ {
if (n.Progress < 1) if (n.Progress < 1)
n.Progress += (float)(Time.Elapsed / 2000) * RNG.NextSingle(); n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
else else
n.State = ProgressNotificationState.Completed; n.State = ProgressNotificationState.Completed;
} }
} }
private void sendProgress2() private void sendDownloadProgress()
{ {
var n = new ProgressNotification var n = new ProgressNotification
{ {
@ -91,9 +134,7 @@ namespace osu.Game.Tests.Visual
progressingNotifications.Add(n); progressingNotifications.Add(n);
} }
private readonly List<ProgressNotification> progressingNotifications = new List<ProgressNotification>(); private void sendUploadProgress()
private void sendProgress1()
{ {
var n = new ProgressNotification var n = new ProgressNotification
{ {
@ -104,14 +145,20 @@ namespace osu.Game.Tests.Visual
progressingNotifications.Add(n); progressingNotifications.Add(n);
} }
private void sendNotification2() private void sendAmazingNotification()
{ {
manager.Post(new SimpleNotification { Text = @"You are amazing" }); 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!" }); 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!!" });
}
} }
} }

View File

@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual
private RulesetStore rulesets; private RulesetStore rulesets;
private DependencyContainer dependencies; private DependencyContainer dependencies;
private WorkingBeatmap defaultBeatmap;
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
@ -47,31 +48,61 @@ namespace osu.Game.Tests.Visual
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); 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] [BackgroundDependencyLoader]
private void load(BeatmapManager baseManager) 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<OsuDbContext> 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. void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () =>
var context = new OsuDbContext(); {
if (deleteMaps) manager.DeleteAll();
Func<OsuDbContext> contextFactory = () => context; if (songSelect != null)
dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, 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) for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i)); 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 Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; });
AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; });

View File

@ -0,0 +1,37 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}
}

View File

@ -15,6 +15,15 @@ namespace osu.Game.Tests.Visual
{ {
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Score),
typeof(Results),
typeof(ResultsPage),
typeof(ResultsPageScore),
typeof(ResultsPageRanking)
};
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps) private void load(BeatmapManager beatmaps)
{ {
@ -41,12 +50,12 @@ namespace osu.Game.Tests.Visual
MaxCombo = 123, MaxCombo = 123,
Rank = ScoreRank.A, Rank = ScoreRank.A,
Date = DateTimeOffset.Now, Date = DateTimeOffset.Now,
Statistics = new Dictionary<string, dynamic> Statistics = new Dictionary<HitResult, dynamic>
{ {
{ "300", 50 }, { HitResult.Great, 50 },
{ "100", 20 }, { HitResult.Good, 20 },
{ "50", 50 }, { HitResult.Meh, 50 },
{ "x", 1 } { HitResult.Miss, 1 }
}, },
User = new User User = new User
{ {

View File

@ -0,0 +1,39 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> 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<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().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);
}
}
}

View File

@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual
{ {
public class TestCaseUserProfile : OsuTestCase public class TestCaseUserProfile : OsuTestCase
{ {
private readonly TestUserProfileOverlay profile;
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(ProfileHeader), typeof(ProfileHeader),
@ -23,8 +25,12 @@ namespace osu.Game.Tests.Visual
public TestCaseUserProfile() public TestCaseUserProfile()
{ {
var profile = new UserProfileOverlay(); Add(profile = new TestUserProfileOverlay());
Add(profile); }
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Show offline dummy", () => profile.ShowUser(new User 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() Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
} }
}, false)); }, false));
checkSupporterTag(false);
AddStep("Show ppy", () => profile.ShowUser(new User AddStep("Show ppy", () => profile.ShowUser(new User
{ {
Username = @"peppy", Username = @"peppy",
@ -55,6 +64,9 @@ namespace osu.Game.Tests.Visual
Country = new Country { FullName = @"Australia", FlagName = @"AU" }, Country = new Country { FullName = @"Australia", FlagName = @"AU" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg"
})); }));
checkSupporterTag(true);
AddStep("Show flyte", () => profile.ShowUser(new User AddStep("Show flyte", () => profile.ShowUser(new User
{ {
Username = @"flyte", Username = @"flyte",
@ -62,8 +74,23 @@ namespace osu.Game.Tests.Visual
Country = new Country { FullName = @"Japan", FlagName = @"JP" }, Country = new Country { FullName = @"Japan", FlagName = @"JP" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
})); }));
AddStep("Hide", profile.Hide); AddStep("Hide", profile.Hide);
AddStep("Show without reload", profile.Show); 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;
}
} }
} }

View File

@ -37,8 +37,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -134,6 +134,7 @@
<Compile Include="Visual\TestCaseOsuGame.cs" /> <Compile Include="Visual\TestCaseOsuGame.cs" />
<Compile Include="Visual\TestCasePlaybackControl.cs" /> <Compile Include="Visual\TestCasePlaybackControl.cs" />
<Compile Include="Visual\TestCasePlaySongSelect.cs" /> <Compile Include="Visual\TestCasePlaySongSelect.cs" />
<Compile Include="Visual\TestCasePopupDialog.cs" />
<Compile Include="Visual\TestCaseRankGraph.cs" /> <Compile Include="Visual\TestCaseRankGraph.cs" />
<Compile Include="Visual\TestCaseReplay.cs" /> <Compile Include="Visual\TestCaseReplay.cs" />
<Compile Include="Visual\TestCaseReplaySettingsOverlay.cs" /> <Compile Include="Visual\TestCaseReplaySettingsOverlay.cs" />
@ -148,6 +149,7 @@
<Compile Include="Visual\TestCaseStoryboard.cs" /> <Compile Include="Visual\TestCaseStoryboard.cs" />
<Compile Include="Visual\TestCaseTabControl.cs" /> <Compile Include="Visual\TestCaseTabControl.cs" />
<Compile Include="Visual\TestCaseTextAwesome.cs" /> <Compile Include="Visual\TestCaseTextAwesome.cs" />
<Compile Include="Visual\TestCaseToolbar.cs" />
<Compile Include="Visual\TestCaseTwoLayerButton.cs" /> <Compile Include="Visual\TestCaseTwoLayerButton.cs" />
<Compile Include="Visual\TestCaseUserPanel.cs" /> <Compile Include="Visual\TestCaseUserPanel.cs" />
<Compile Include="Visual\TestCaseUserProfile.cs" /> <Compile Include="Visual\TestCaseUserProfile.cs" />

View File

@ -6,6 +6,6 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
<packages> <packages>
<package id="DeepEqual" version="1.6.0.0" targetFramework="net461" /> <package id="DeepEqual" version="1.6.0.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net461" /> <package id="System.ValueTuple" version="4.4.0" targetFramework="net461" />
</packages> </packages>

View File

@ -14,10 +14,20 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal"; public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap"; 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}"); SampleChannel channel = null;
channel.Volume.Value = Volume / 100.0;
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; return channel;
} }

View File

@ -1,18 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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<SampleInfo>
{
public SampleInfoList()
{
}
public SampleInfoList(IEnumerable<SampleInfo> elements) : base(elements)
{
}
}
}

View File

@ -697,10 +697,12 @@ namespace osu.Game.Beatmaps
} }
} }
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
/// <summary> /// <summary>
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
/// </summary> /// </summary>
public void ImportFromStable() public async Task ImportFromStable()
{ {
var stable = GetStableStorage?.Invoke(); var stable = GetStableStorage?.Invoke();
@ -710,7 +712,7 @@ namespace osu.Game.Beatmaps
return; return;
} }
Import(stable.GetDirectories("Songs")); await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning);
} }
public void DeleteAll() public void DeleteAll()

View File

@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.ControlPoints
/// <summary> /// <summary>
/// The default sample volume at this control point. /// The default sample volume at this control point.
/// </summary> /// </summary>
public int SampleVolume; public int SampleVolume = 100;
/// <summary> /// <summary>
/// Create a SampleInfo based on the sample settings in this control point. /// Create a SampleInfo based on the sample settings in this control point.

View File

@ -21,8 +21,7 @@ namespace osu.Game.Beatmaps
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
Artist = "please load a beatmap!", Artist = "please load a beatmap!",
Title = "no beatmaps available!", Title = "no beatmaps available!"
AuthorString = "no one",
}, },
BeatmapSet = new BeatmapSetInfo(), BeatmapSet = new BeatmapSetInfo(),
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty

View File

@ -122,26 +122,26 @@ namespace osu.Game.Online.API.Requests
{ {
foreach (var kvp in value) foreach (var kvp in value)
{ {
string key = kvp.Key; HitResult newKey;
switch (key) switch (kvp.Key)
{ {
case @"count_300": case @"count_300":
key = @"300"; newKey = HitResult.Great;
break; break;
case @"count_100": case @"count_100":
key = @"100"; newKey = HitResult.Good;
break; break;
case @"count_50": case @"count_50":
key = @"50"; newKey = HitResult.Meh;
break; break;
case @"count_miss": case @"count_miss":
key = @"x"; newKey = HitResult.Miss;
break; break;
default: default:
continue; continue;
} }
Statistics.Add(key, kvp.Value); Statistics.Add(newKey, kvp.Value);
} }
} }
} }

View File

@ -16,6 +16,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using OpenTK; using OpenTK;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -37,7 +38,7 @@ namespace osu.Game
private MusicController musicController; private MusicController musicController;
private NotificationOverlay notificationOverlay; private NotificationOverlay notifications;
private DialogOverlay dialogOverlay; private DialogOverlay dialogOverlay;
@ -64,6 +65,8 @@ namespace osu.Game
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
public readonly BindableBool ShowOverlays = new BindableBool();
private OsuScreen screenStack; private OsuScreen screenStack;
private VolumeControl volume; private VolumeControl volume;
@ -136,7 +139,7 @@ namespace osu.Game
if (s.Beatmap == null) 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!", Text = @"Tried to load a score for a beatmap we don't have!",
Icon = FontAwesome.fa_life_saver, Icon = FontAwesome.fa_life_saver,
@ -154,7 +157,7 @@ namespace osu.Game
base.LoadComplete(); base.LoadComplete();
// hook up notifications to components. // hook up notifications to components.
BeatmapManager.PostNotification = n => notificationOverlay?.Post(n); BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall; BeatmapManager.GetStableStorage = GetStorageForStableInstall;
AddRange(new Drawable[] AddRange(new Drawable[]
@ -207,8 +210,9 @@ namespace osu.Game
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}, overlayContent.Add); }, overlayContent.Add);
loadComponentSingleFile(notificationOverlay = new NotificationOverlay loadComponentSingleFile(notifications = new NotificationOverlay
{ {
GetToolbarHeight = () => ToolbarOffset,
Depth = -4, Depth = -4,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
@ -219,15 +223,7 @@ namespace osu.Game
Depth = -6, Depth = -6,
}, overlayContent.Add); }, overlayContent.Add);
Logger.NewEntry += entry => forwardLoggedErrorsToNotifications();
{
if (entry.Level < LogLevel.Important) return;
notificationOverlay.Post(new SimpleNotification
{
Text = $@"{entry.Level}: {entry.Message}"
});
};
dependencies.Cache(settings); dependencies.Cache(settings);
dependencies.Cache(social); dependencies.Cache(social);
@ -236,7 +232,7 @@ namespace osu.Game
dependencies.Cache(userProfile); dependencies.Cache(userProfile);
dependencies.Cache(musicController); dependencies.Cache(musicController);
dependencies.Cache(beatmapSetOverlay); dependencies.Cache(beatmapSetOverlay);
dependencies.Cache(notificationOverlay); dependencies.Cache(notifications);
dependencies.Cache(dialogOverlay); dependencies.Cache(dialogOverlay);
// ensure only one of these overlays are open at once. // 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: hideAllOverlays();
intro.MoveToX(0, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); musicController.State = Visibility.Hidden;
break; Toolbar.State = Visibility.Hidden;
case Visibility.Visible:
intro.MoveToX(SettingsOverlay.SIDEBAR_WIDTH / 2, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
break;
} }
else
Toolbar.State = Visibility.Visible;
}; };
Cursor.State = Visibility.Hidden; 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 Task asyncLoadStream;
private void loadComponentSingleFile<T>(T d, Action<T> add) private void loadComponentSingleFile<T>(T d, Action<T> add)
@ -335,8 +378,6 @@ namespace osu.Game
public bool OnReleased(GlobalAction action) => false; public bool OnReleased(GlobalAction action) => false;
public event Action<Screen> ScreenChanged;
private Container mainContent; private Container mainContent;
private Container overlayContent; private Container overlayContent;
@ -351,30 +392,7 @@ namespace osu.Game
direct.State = Visibility.Hidden; direct.State = Visibility.Hidden;
social.State = Visibility.Hidden; social.State = Visibility.Hidden;
userProfile.State = Visibility.Hidden; userProfile.State = Visibility.Hidden;
notificationOverlay.State = Visibility.Hidden; notifications.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);
} }
protected override bool OnExiting() protected override bool OnExiting()
@ -422,15 +440,18 @@ namespace osu.Game
private void screenAdded(Screen newScreen) private void screenAdded(Screen newScreen)
{ {
currentScreen = (OsuScreen)newScreen;
newScreen.ModePushed += screenAdded; newScreen.ModePushed += screenAdded;
newScreen.Exited += screenRemoved; newScreen.Exited += screenRemoved;
screenChanged(newScreen);
} }
private void screenRemoved(Screen newScreen) private void screenRemoved(Screen newScreen)
{ {
screenChanged(newScreen); currentScreen = (OsuScreen)newScreen;
if (newScreen == null)
Exit();
} }
} }
} }

View File

@ -177,8 +177,7 @@ namespace osu.Game
} }
catch (MigrationFailedException e) catch (MigrationFailedException e)
{ {
Logger.Log((e.InnerException ?? e).ToString(), LoggingTarget.Database, LogLevel.Error); Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database);
Logger.Log("Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database, LogLevel.Error);
// if we failed, let's delete the database and start fresh. // 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. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -176,7 +177,7 @@ namespace osu.Game.Overlays.BeatmapSet
Shadow = false, Shadow = false,
Margin = new MarginPadding { Top = 20 }, Margin = new MarginPadding { Top = 20 },
}, },
textFlow = new TextFlowContainer textFlow = new OsuTextFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users; using osu.Game.Users;
@ -48,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Font = @"Exo2.0-RegularItalic", Font = @"Exo2.0-RegularItalic",
Margin = new MarginPadding { Left = side_margin } Margin = new MarginPadding { Left = side_margin }
}, },
new DrawableFlag(score.User.Country?.FlagName) new DrawableFlag(score.User.Country)
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
@ -104,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = 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", Font = @"Exo2.0-RegularItalic",
Margin = new MarginPadding { Right = side_margin } Margin = new MarginPadding { Right = side_margin }
}, },

View File

@ -52,13 +52,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
score = value; score = value;
avatar.User = username.User = score.User; 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}"; date.Text = $@"achieved {score.Date:MMM d, yyyy}";
rank.UpdateRank(score.Rank); rank.UpdateRank(score.Rank);
totalScore.Value = $@"{score.TotalScore:N0}"; totalScore.Value = $@"{score.TotalScore:N0}";
accuracy.Value = $@"{score.Accuracy:P2}"; 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(); modsContainer.Clear();
foreach (Mod mod in score.Mods) foreach (Mod mod in score.Mods)

View File

@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog
private readonly FillFlowContainer<PopupDialogButton> buttonsContainer; private readonly FillFlowContainer<PopupDialogButton> buttonsContainer;
private readonly SpriteIcon icon; private readonly SpriteIcon icon;
private readonly SpriteText header; private readonly SpriteText header;
private readonly SpriteText body; private readonly TextFlowContainer body;
public FontAwesome Icon public FontAwesome Icon
{ {
@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Dialog
public string BodyText public string BodyText
{ {
get { return body.Text; }
set { body.Text = value; } set { body.Text = value; }
} }
@ -220,17 +219,15 @@ namespace osu.Game.Overlays.Dialog
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Text = @"Header",
TextSize = 25, TextSize = 25,
Shadow = true, Shadow = true,
}, },
body = new OsuSpriteText body = new OsuTextFlowContainer(t => t.TextSize = 18)
{ {
Origin = Anchor.TopCentre, Padding = new MarginPadding(15),
Anchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X,
Text = @"Body", AutoSizeAxes = Axes.Y,
TextSize = 18, TextAnchor = Anchor.TopCentre,
Shadow = true,
}, },
}, },
}, },

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
@ -88,7 +89,7 @@ namespace osu.Game.Overlays.MedalSplash
Alpha = 0f, Alpha = 0f,
Scale = new Vector2(1f / scale_when_full), Scale = new Vector2(1f / scale_when_full),
}, },
description = new TextFlowContainer description = new OsuTextFlowContainer
{ {
TextAnchor = Anchor.TopCentre, TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,

View File

@ -204,13 +204,13 @@ namespace osu.Game.Overlays.Mods
{ {
iconsContainer.AddRange(new[] iconsContainer.AddRange(new[]
{ {
backgroundIcon = new ModIcon(Mods[1]) backgroundIcon = new PassThroughTooltipModIcon(Mods[1])
{ {
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Position = new Vector2(1.5f), Position = new Vector2(1.5f),
}, },
foregroundIcon = new ModIcon(Mods[0]) foregroundIcon = new PassThroughTooltipModIcon(Mods[0])
{ {
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Mods
} }
else else
{ {
iconsContainer.Add(foregroundIcon = new ModIcon(Mod) iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -259,5 +259,14 @@ namespace osu.Game.Overlays.Mods
Mod = mod; Mod = mod;
} }
private class PassThroughTooltipModIcon : ModIcon
{
public override string TooltipText => null;
public PassThroughTooltipModIcon(Mod mod) : base(mod)
{
}
}
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -10,6 +9,10 @@ using osu.Game.Overlays.Notifications;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Threading;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -19,9 +22,39 @@ namespace osu.Game.Overlays
public const float TRANSITION_LENGTH = 600; public const float TRANSITION_LENGTH = 600;
private ScrollContainer scrollContainer; /// <summary>
/// Whether posted notifications should be processed.
/// </summary>
public readonly BindableBool Enabled = new BindableBool(true);
private FlowContainer<NotificationSection> sections; private FlowContainer<NotificationSection> sections;
/// <summary>
/// Provide a source for the toolbar height.
/// </summary>
public Func<float> 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] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -36,12 +69,12 @@ namespace osu.Game.Overlays
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
Alpha = 0.6f, Alpha = 0.6f
}, },
scrollContainer = new OsuScrollContainer new OsuScrollContainer
{ {
Masking = true,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT },
Children = new[] Children = new[]
{ {
sections = new FillFlowContainer<NotificationSection> sections = new FillFlowContainer<NotificationSection>
@ -55,14 +88,14 @@ namespace osu.Game.Overlays
{ {
Title = @"Notifications", Title = @"Notifications",
ClearText = @"Clear All", ClearText = @"Clear All",
AcceptTypes = new[] { typeof(SimpleNotification) }, AcceptTypes = new[] { typeof(SimpleNotification) }
}, },
new NotificationSection new NotificationSection
{ {
Title = @"Running Tasks", Title = @"Running Tasks",
ClearText = @"Cancel All", 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 int runningDepth;
private void notificationClosed() 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(() => Schedule(() =>
{ {
State = Visibility.Visible; // hide ourselves if all notifications have been dismissed.
if (totalCount == 0)
++runningDepth; State = Visibility.Hidden;
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();
}
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() protected override void PopIn()
{ {
base.PopIn(); base.PopIn();
scrollContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(1, TRANSITION_LENGTH / 2); this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint);
}
private void markAllRead()
{
sections.Children.ForEach(s => s.MarkAllRead());
} }
protected override void PopOut() protected override void PopOut()
@ -120,7 +165,26 @@ namespace osu.Game.Overlays
markAllRead(); markAllRead();
this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint); 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 };
} }
} }
} }

View File

@ -91,7 +91,6 @@ namespace osu.Game.Overlays.Notifications
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding Padding = new MarginPadding
{ {
Top = 5,
Left = 45, Left = 45,
Right = 30 Right = 30
}, },
@ -261,4 +260,4 @@ namespace osu.Game.Overlays.Notifications
} }
} }
} }
} }

View File

@ -15,7 +15,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Notifications namespace osu.Game.Overlays.Notifications
{ {
public class NotificationSection : FillFlowContainer public class NotificationSection : AlwaysUpdateFillFlowContainer<Drawable>
{ {
private OsuSpriteText titleText; private OsuSpriteText titleText;
private OsuSpriteText countText; private OsuSpriteText countText;
@ -26,11 +26,14 @@ namespace osu.Game.Overlays.Notifications
public int DisplayedCount => notifications.Count(n => !n.WasClosed); 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 void Add(Notification notification) => notifications.Add(notification);
public IEnumerable<Type> AcceptTypes; public IEnumerable<Type> AcceptTypes;
private string clearText; private string clearText;
public string ClearText public string ClearText
{ {
get { return clearText; } get { return clearText; }
@ -108,7 +111,7 @@ namespace osu.Game.Overlays.Notifications
}, },
}, },
}, },
notifications = new FillFlowContainer<Notification> notifications = new AlwaysUpdateFillFlowContainer<Notification>
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -157,4 +160,13 @@ namespace osu.Game.Overlays.Notifications
notifications?.Children.ForEach(n => n.Read = true); notifications?.Children.ForEach(n => n.Read = true);
} }
} }
}
public class AlwaysUpdateFillFlowContainer<T> : FillFlowContainer<T>
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;
}
}

Some files were not shown because too many files have changed in this diff Show More