1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 05:43:18 +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
- A desktop platform which can compile .NET 4.5 (tested on macOS, linux and windows). We recommend using [Visual Studio Code](https://code.visualstudio.com/) (all platforms) or [Visual Studio Community Edition](https://www.visualstudio.com/) (windows only), both of which are free.
- Make sure you initialise and keep submodules up-to-date.
- A desktop platform that can compile .NET 4.6.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here.
# Getting Started
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`)
# Contributing

@ -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).
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>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
<package id="DeltaCompressionDotNet" version="1.1.0" 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="Splat" version="2.0.0" targetFramework="net45" />
<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.Objects.Drawables;
using OpenTK;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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>

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
};
if (hold.Head.Samples == null)
hold.Head.Samples = new SampleInfoList();
hold.Head.Samples = new List<SampleInfo>();
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
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{

View File

@ -1,7 +1,7 @@
// 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.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
@ -24,4 +24,4 @@ namespace osu.Game.Rulesets.Mania.Judgements
}
}
}
}
}

View File

@ -1,7 +1,7 @@
// 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.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
@ -11,4 +11,4 @@ namespace osu.Game.Rulesets.Mania.Judgements
protected override int NumericResultFor(HitResult result) => 20;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Tests.Visual;

View File

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

View File

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

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Mods
{
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy
{
}
public class OsuModHidden : ModHidden
public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
{
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT;
public void ApplyToDrawableHitObjects(IEnumerable<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>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
}
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
}
public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect
{
}
public class OsuModSpunOut : Mod

View File

@ -6,8 +6,8 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number;
private readonly GlowPiece glow;
public DrawableHitCircle(OsuHitObject h) : base(h)
public DrawableHitCircle(HitCircle h) : base(h)
{
Origin = Anchor.Centre;
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
number = new NumberPiece
{
Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(),
Text = (HitObject.ComboIndex + 1).ToString(),
},
ring = new RingPiece(),
flash = new FlashPiece(),
@ -88,25 +88,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT));
ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT));
ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
}
protected override void UpdateCurrentState(ArmedState state)
{
double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime;
glow.Delay(duration).FadeOut(400);
glow.FadeOut(400);
switch (state)
{
case ArmedState.Idle:
this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT);
this.Delay(TIME_PREEMPT).FadeOut(500);
Expire(true);
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
LifetimeEnd = HitObject.StartTime + HitObject.HitWindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(TIME_FADEOUT / 5);
this.FadeOut(100);
Expire();
break;
case ArmedState.Hit:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{

View File

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

View File

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

View File

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

View File

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

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)
{
var circle = h as HitCircle;
if (circle != null)
if (h is HitCircle circle)
return new DrawableHitCircle(circle);
var slider = h as Slider;
if (slider != null)
if (h is Slider slider)
return new DrawableSlider(slider);
var spinner = h as Spinner;
if (spinner != null)
if (h is Spinner spinner)
return new DrawableSpinner(spinner);
return null;
}

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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>

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;
// 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);
@ -115,12 +115,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
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;
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);
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>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
@ -20,4 +20,4 @@ namespace osu.Game.Rulesets.Taiko.Judgements
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
validKeyPressed = HitActions.Contains(action);
// Only count this as handled if the new judgement is a hit
return UpdateJudgement(true) && Judgements.LastOrDefault()?.IsHit == true;
return UpdateJudgement(true);
}
protected override void Update()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
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;
}
}

View File

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

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

View File

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

View File

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

View File

@ -16,17 +16,11 @@ using osu.Framework.Extensions.Color4Extensions;
using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using System.Collections.Generic;
using osu.Game.Audio;
using System;
namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoPlayfield : ScrollingPlayfield, IKeyBindingHandler<TaikoAction>
public class TaikoPlayfield : ScrollingPlayfield
{
/// <summary>
/// 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 background;
private readonly ControlPointInfo controlPointInfo;
private Dictionary<SampleControlPoint, DrumSamples> drumSampleMappings;
public TaikoPlayfield(ControlPointInfo controlPointInfo)
public TaikoPlayfield(ControlPointInfo controlPoints)
: base(Axes.X)
{
this.controlPointInfo = controlPointInfo;
AddRangeInternal(new Drawable[]
{
backgroundContainer = new Container
@ -160,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
new InputDrum(controlPoints)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
@ -205,19 +195,8 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
private void load(OsuColour colours)
{
drumSampleMappings = new Dictionary<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;
overlayBackground.Colour = colours.Gray1;
@ -282,28 +261,5 @@ namespace osu.Game.Rulesets.Taiko.UI
kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim));
}
}
public bool OnPressed(TaikoAction action)
{
var samplePoint = controlPointInfo.SamplePointAt(Clock.CurrentTime);
if (!drumSampleMappings.TryGetValue(samplePoint, out var samples))
throw new InvalidOperationException("Current sample set not found.");
if (action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre)
samples.Centre.Play();
else
samples.Rim.Play();
return true;
}
public bool OnReleased(TaikoAction action) => false;
private class DrumSamples
{
public SampleChannel Centre;
public SampleChannel Rim;
}
}
}

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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>

View File

@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual
testRemoveAll();
testEmptyTraversal();
testHiding();
}
private void ensureRandomFetchSuccess() =>
@ -295,6 +296,40 @@ namespace osu.Game.Tests.Visual
checkNoSelection();
}
private void testHiding()
{
var hidingSet = createTestBeatmapSet(1);
hidingSet.Beatmaps[1].Hidden = true;
AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet));
setSelected(1, 1);
checkVisibleItemCount(true, 2);
advanceSelection(true);
checkSelected(1, 3);
setHidden(3);
checkSelected(1, 1);
setHidden(2, false);
advanceSelection(true);
checkSelected(1, 2);
setHidden(1);
checkSelected(1, 2);
setHidden(2);
checkNoSelection();
void setHidden(int diff, bool hidden = true)
{
AddStep((hidden ? "" : "un") + $"hide diff {diff}", () =>
{
hidingSet.Beatmaps[diff - 1].Hidden = hidden;
carousel.UpdateBeatmapSet(hidingSet);
});
}
}
private BeatmapSetInfo createTestBeatmapSet(int i)
{
return new BeatmapSetInfo

View File

@ -1,69 +1,161 @@
// 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 OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
public class TestCaseBeatmapInfoWedge : OsuTestCase
{
private BeatmapManager beatmaps;
private readonly Random random;
private readonly BeatmapInfoWedge infoWedge;
private RulesetStore rulesets;
private TestBeatmapInfoWedge infoWedge;
private readonly List<Beatmap> beatmaps = new List<Beatmap>();
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),
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding
{
Top = 20,
},
Margin = new MarginPadding { Top = 20 }
});
AddStep("show", () =>
{
Content.FadeInFromZero(250);
infoWedge.State = Visibility.Visible;
infoWedge.UpdateBeatmap(beatmap);
});
AddStep("hide", () =>
AddWaitStep(3);
AddStep("hide", () => { infoWedge.State = Visibility.Hidden; });
AddWaitStep(3);
AddStep("show", () => { infoWedge.State = Visibility.Visible; });
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
infoWedge.State = Visibility.Hidden;
Content.FadeOut(100);
var ruleset = rulesetInfo.CreateInstance();
beatmaps.Add(createTestBeatmap(rulesetInfo));
var name = rulesetInfo.ShortName;
selectBeatmap(name);
// TODO: adjust cases once more info is shown for other gamemodes
switch (ruleset)
{
case OsuRuleset osu:
testOsuBeatmap(osu);
testInfoLabels(5);
break;
default:
testInfoLabels(2);
break;
}
}
testNullBeatmap();
}
private void testOsuBeatmap(OsuRuleset ruleset)
{
AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist");
AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType<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 load(OsuGameBase game, BeatmapManager beatmaps)
private void selectNullBeatmap()
{
this.beatmaps = beatmaps;
beatmap.BindTo(game.Beatmap);
AddStep("select null beatmap", () =>
{
beatmap.Value = beatmap.Default;
infoWedge.UpdateBeatmap(beatmap);
});
}
private void randomBeatmap()
private Beatmap createTestBeatmap(RulesetInfo ruleset)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count == 0)
return;
List<HitObject> objects = new List<HitObject>();
for (double i = 0; i < 50000; i += 1000)
objects.Add(new HitObject { StartTime = i });
var b = sets[random.Next(0, sets.Count)].Beatmaps[0];
beatmap.Value = beatmaps.GetWorkingBeatmap(b);
infoWedge.UpdateBeatmap(beatmap);
return new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
AuthorString = $"{ruleset.ShortName}Author",
Artist = $"{ruleset.ShortName}Artist",
Source = $"{ruleset.ShortName}Source",
Title = $"{ruleset.ShortName}Title"
},
Ruleset = ruleset,
StarDifficulty = 6,
Version = $"{ruleset.ShortName}Version"
},
HitObjects = objects
};
}
private class TestBeatmapInfoWedge : BeatmapInfoWedge
{
public new BufferedWedgeInfo Info => base.Info;
}
}
}

View File

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

View File

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

View File

@ -1,21 +1,32 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseNotificationOverlay : OsuTestCase
{
private readonly NotificationOverlay manager;
private readonly List<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()
{
@ -24,33 +35,65 @@ namespace osu.Game.Tests.Visual
Content.Add(manager = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Origin = Anchor.TopRight
});
AddToggleStep(@"show", state => manager.State = state ? Visibility.Visible : Visibility.Hidden);
SpriteText displayedCount = new SpriteText();
AddStep(@"simple #1", sendNotification1);
AddStep(@"simple #2", sendNotification2);
AddStep(@"progress #1", sendProgress1);
AddStep(@"progress #2", sendProgress2);
AddStep(@"barrage", () => sendBarrage());
Content.Add(displayedCount);
void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state);
void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; };
setState(Visibility.Visible);
AddStep(@"simple #1", sendHelloNotification);
AddStep(@"simple #2", sendAmazingNotification);
AddStep(@"progress #1", sendUploadProgress);
AddStep(@"progress #2", sendDownloadProgress);
checkProgressingCount(2);
setState(Visibility.Hidden);
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
AddWaitStep(5);
checkProgressingCount(0);
AddStep(@"progress #3", sendUploadProgress);
checkProgressingCount(1);
AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33);
AddWaitStep(10);
checkProgressingCount(0);
setState(Visibility.Visible);
//AddStep(@"barrage", () => sendBarrage());
}
private void sendBarrage(int remaining = 100)
private void sendBarrage(int remaining = 10)
{
switch (RNG.Next(0, 4))
{
case 0:
sendNotification1();
sendHelloNotification();
break;
case 1:
sendNotification2();
sendAmazingNotification();
break;
case 2:
sendProgress1();
sendUploadProgress();
break;
case 3:
sendProgress2();
sendDownloadProgress();
break;
}
@ -66,7 +109,7 @@ namespace osu.Game.Tests.Visual
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
{
var p = progressingNotifications.FirstOrDefault(n => n.IsAlive && n.State == ProgressNotificationState.Queued);
var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued);
if (p != null)
p.State = ProgressNotificationState.Active;
}
@ -74,13 +117,13 @@ namespace osu.Game.Tests.Visual
foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
{
if (n.Progress < 1)
n.Progress += (float)(Time.Elapsed / 2000) * RNG.NextSingle();
n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
else
n.State = ProgressNotificationState.Completed;
}
}
private void sendProgress2()
private void sendDownloadProgress()
{
var n = new ProgressNotification
{
@ -91,9 +134,7 @@ namespace osu.Game.Tests.Visual
progressingNotifications.Add(n);
}
private readonly List<ProgressNotification> progressingNotifications = new List<ProgressNotification>();
private void sendProgress1()
private void sendUploadProgress()
{
var n = new ProgressNotification
{
@ -104,14 +145,20 @@ namespace osu.Game.Tests.Visual
progressingNotifications.Add(n);
}
private void sendNotification2()
private void sendAmazingNotification()
{
manager.Post(new SimpleNotification { Text = @"You are amazing" });
}
private void sendNotification1()
private void sendHelloNotification()
{
manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
}
private void sendManyNotifications()
{
for (int i = 0; i < 10; i++)
manager.Post(new SimpleNotification { Text = @"Spam incoming!!" });
}
}
}

View File

@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual
private RulesetStore rulesets;
private DependencyContainer dependencies;
private WorkingBeatmap defaultBeatmap;
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);
private class TestSongSelect : PlaySongSelect
{
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public new BeatmapCarousel Carousel => base.Carousel;
}
[BackgroundDependencyLoader]
private void load(BeatmapManager baseManager)
{
PlaySongSelect songSelect;
TestSongSelect songSelect = null;
if (manager == null)
var storage = new TestStorage(@"TestCasePlaySongSelect");
// this is by no means clean. should be replacing inside of OsuGameBase somehow.
var context = new OsuDbContext();
Func<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.
var context = new OsuDbContext();
void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () =>
{
if (deleteMaps) manager.DeleteAll();
Func<OsuDbContext> contextFactory = () => context;
dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
if (songSelect != null)
{
DefaultBeatmap = baseManager.GetWorkingBeatmap(null)
});
Remove(songSelect);
songSelect.Dispose();
}
Add(songSelect = new TestSongSelect());
});
loadNewSongSelect(true);
AddWaitStep(3);
AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap);
AddStep("import test maps", () =>
{
for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i));
}
});
Add(songSelect = new PlaySongSelect());
AddWaitStep(3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
loadNewSongSelect();
AddWaitStep(3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; });
AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; });

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;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Score),
typeof(Results),
typeof(ResultsPage),
typeof(ResultsPageScore),
typeof(ResultsPageRanking)
};
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
@ -41,12 +50,12 @@ namespace osu.Game.Tests.Visual
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<string, dynamic>
Statistics = new Dictionary<HitResult, dynamic>
{
{ "300", 50 },
{ "100", 20 },
{ "50", 50 },
{ "x", 1 }
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
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
{
private readonly TestUserProfileOverlay profile;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ProfileHeader),
@ -23,8 +25,12 @@ namespace osu.Game.Tests.Visual
public TestCaseUserProfile()
{
var profile = new UserProfileOverlay();
Add(profile);
Add(profile = new TestUserProfileOverlay());
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Show offline dummy", () => profile.ShowUser(new User
{
@ -48,6 +54,9 @@ namespace osu.Game.Tests.Visual
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
}
}, false));
checkSupporterTag(false);
AddStep("Show ppy", () => profile.ShowUser(new User
{
Username = @"peppy",
@ -55,6 +64,9 @@ namespace osu.Game.Tests.Visual
Country = new Country { FullName = @"Australia", FlagName = @"AU" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg"
}));
checkSupporterTag(true);
AddStep("Show flyte", () => profile.ShowUser(new User
{
Username = @"flyte",
@ -62,8 +74,23 @@ namespace osu.Game.Tests.Visual
Country = new Country { FullName = @"Japan", FlagName = @"JP" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}));
AddStep("Hide", profile.Hide);
AddStep("Show without reload", profile.Show);
}
private void checkSupporterTag(bool isSupporter)
{
AddUntilStep(() => profile.Header.User != null, "wait for load");
if (isSupporter)
AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1);
else
AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0);
}
private class TestUserProfileOverlay : UserProfileOverlay
{
public new ProfileHeader Header => base.Header;
}
}
}

View File

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

View File

@ -14,10 +14,20 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap";
public SampleChannel GetChannel(SampleManager manager)
public SampleChannel GetChannel(SampleManager manager, string resourceNamespace = null)
{
var channel = manager.Get($"Gameplay/{Bank}-{Name}");
channel.Volume.Value = Volume / 100.0;
SampleChannel channel = null;
if (resourceNamespace != null)
channel = manager.Get($"Gameplay/{resourceNamespace}/{Bank}-{Name}");
// try without namespace as a fallback.
if (channel == null)
channel = manager.Get($"Gameplay/{Bank}-{Name}");
if (channel != null)
channel.Volume.Value = Volume / 100.0;
return channel;
}

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

View File

@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.ControlPoints
/// <summary>
/// The default sample volume at this control point.
/// </summary>
public int SampleVolume;
public int SampleVolume = 100;
/// <summary>
/// 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
{
Artist = "please load a beatmap!",
Title = "no beatmaps available!",
AuthorString = "no one",
Title = "no beatmaps available!"
},
BeatmapSet = new BeatmapSetInfo(),
BaseDifficulty = new BeatmapDifficulty

View File

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

View File

@ -16,6 +16,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Menu;
using OpenTK;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Input.Bindings;
using osu.Framework.Platform;
@ -37,7 +38,7 @@ namespace osu.Game
private MusicController musicController;
private NotificationOverlay notificationOverlay;
private NotificationOverlay notifications;
private DialogOverlay dialogOverlay;
@ -64,6 +65,8 @@ namespace osu.Game
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
public readonly BindableBool ShowOverlays = new BindableBool();
private OsuScreen screenStack;
private VolumeControl volume;
@ -136,7 +139,7 @@ namespace osu.Game
if (s.Beatmap == null)
{
notificationOverlay.Post(new SimpleNotification
notifications.Post(new SimpleNotification
{
Text = @"Tried to load a score for a beatmap we don't have!",
Icon = FontAwesome.fa_life_saver,
@ -154,7 +157,7 @@ namespace osu.Game
base.LoadComplete();
// hook up notifications to components.
BeatmapManager.PostNotification = n => notificationOverlay?.Post(n);
BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
AddRange(new Drawable[]
@ -207,8 +210,9 @@ namespace osu.Game
Origin = Anchor.TopRight,
}, overlayContent.Add);
loadComponentSingleFile(notificationOverlay = new NotificationOverlay
loadComponentSingleFile(notifications = new NotificationOverlay
{
GetToolbarHeight = () => ToolbarOffset,
Depth = -4,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@ -219,15 +223,7 @@ namespace osu.Game
Depth = -6,
}, overlayContent.Add);
Logger.NewEntry += entry =>
{
if (entry.Level < LogLevel.Important) return;
notificationOverlay.Post(new SimpleNotification
{
Text = $@"{entry.Level}: {entry.Message}"
});
};
forwardLoggedErrorsToNotifications();
dependencies.Cache(settings);
dependencies.Cache(social);
@ -236,7 +232,7 @@ namespace osu.Game
dependencies.Cache(userProfile);
dependencies.Cache(musicController);
dependencies.Cache(beatmapSetOverlay);
dependencies.Cache(notificationOverlay);
dependencies.Cache(notifications);
dependencies.Cache(dialogOverlay);
// ensure only one of these overlays are open at once.
@ -271,22 +267,69 @@ namespace osu.Game
};
}
settings.StateChanged += delegate
void updateScreenOffset()
{
switch (settings.State)
float offset = 0;
if (settings.State == Visibility.Visible)
offset += ToolbarButton.WIDTH / 2;
if (notifications.State == Visibility.Visible)
offset -= ToolbarButton.WIDTH / 2;
screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
}
settings.StateChanged += _ => updateScreenOffset();
notifications.StateChanged += _ => updateScreenOffset();
notifications.Enabled.BindTo(ShowOverlays);
ShowOverlays.ValueChanged += visible =>
{
//central game screen change logic.
if (!visible)
{
case Visibility.Hidden:
intro.MoveToX(0, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
break;
case Visibility.Visible:
intro.MoveToX(SettingsOverlay.SIDEBAR_WIDTH / 2, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
break;
hideAllOverlays();
musicController.State = Visibility.Hidden;
Toolbar.State = Visibility.Hidden;
}
else
Toolbar.State = Visibility.Visible;
};
Cursor.State = Visibility.Hidden;
}
private void forwardLoggedErrorsToNotifications()
{
int recentErrorCount = 0;
const double debounce = 5000;
Logger.NewEntry += entry =>
{
if (entry.Level < LogLevel.Error || entry.Target == null) return;
if (recentErrorCount < 2)
{
notifications.Post(new SimpleNotification
{
Icon = FontAwesome.fa_bomb,
Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.",
Activated = () =>
{
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
return true;
}
});
}
Interlocked.Increment(ref recentErrorCount);
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce);
};
}
private Task asyncLoadStream;
private void loadComponentSingleFile<T>(T d, Action<T> add)
@ -335,8 +378,6 @@ namespace osu.Game
public bool OnReleased(GlobalAction action) => false;
public event Action<Screen> ScreenChanged;
private Container mainContent;
private Container overlayContent;
@ -351,30 +392,7 @@ namespace osu.Game
direct.State = Visibility.Hidden;
social.State = Visibility.Hidden;
userProfile.State = Visibility.Hidden;
notificationOverlay.State = Visibility.Hidden;
}
private void screenChanged(Screen newScreen)
{
currentScreen = newScreen as OsuScreen;
if (currentScreen == null)
{
Exit();
return;
}
//central game screen change logic.
if (!currentScreen.ShowOverlays)
{
hideAllOverlays();
musicController.State = Visibility.Hidden;
Toolbar.State = Visibility.Hidden;
}
else
Toolbar.State = Visibility.Visible;
ScreenChanged?.Invoke(newScreen);
notifications.State = Visibility.Hidden;
}
protected override bool OnExiting()
@ -422,15 +440,18 @@ namespace osu.Game
private void screenAdded(Screen newScreen)
{
currentScreen = (OsuScreen)newScreen;
newScreen.ModePushed += screenAdded;
newScreen.Exited += screenRemoved;
screenChanged(newScreen);
}
private void screenRemoved(Screen newScreen)
{
screenChanged(newScreen);
currentScreen = (OsuScreen)newScreen;
if (newScreen == null)
Exit();
}
}
}

View File

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

View File

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

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
@ -48,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Font = @"Exo2.0-RegularItalic",
Margin = new MarginPadding { Left = side_margin }
},
new DrawableFlag(score.User.Country?.FlagName)
new DrawableFlag(score.User.Country)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@ -104,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Text = $"{score.Statistics["300"]}/{score.Statistics["100"]}/{score.Statistics["50"]}",
Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}",
Font = @"Exo2.0-RegularItalic",
Margin = new MarginPadding { Right = side_margin }
},

View File

@ -52,13 +52,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
score = value;
avatar.User = username.User = score.User;
flag.FlagName = score.User.Country?.FlagName;
flag.Country = score.User.Country;
date.Text = $@"achieved {score.Date:MMM d, yyyy}";
rank.UpdateRank(score.Rank);
totalScore.Value = $@"{score.TotalScore:N0}";
accuracy.Value = $@"{score.Accuracy:P2}";
statistics.Value = $"{score.Statistics["300"]}/{score.Statistics["100"]}/{score.Statistics["50"]}";
statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}";
modsContainer.Clear();
foreach (Mod mod in score.Mods)

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -10,6 +9,10 @@ using osu.Game.Overlays.Notifications;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Threading;
namespace osu.Game.Overlays
{
@ -19,9 +22,39 @@ namespace osu.Game.Overlays
public const float TRANSITION_LENGTH = 600;
private ScrollContainer scrollContainer;
/// <summary>
/// Whether posted notifications should be processed.
/// </summary>
public readonly BindableBool Enabled = new BindableBool(true);
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]
private void load()
{
@ -36,12 +69,12 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.6f,
Alpha = 0.6f
},
scrollContainer = new OsuScrollContainer
new OsuScrollContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT },
Children = new[]
{
sections = new FillFlowContainer<NotificationSection>
@ -55,14 +88,14 @@ namespace osu.Game.Overlays
{
Title = @"Notifications",
ClearText = @"Clear All",
AcceptTypes = new[] { typeof(SimpleNotification) },
AcceptTypes = new[] { typeof(SimpleNotification) }
},
new NotificationSection
{
Title = @"Running Tasks",
ClearText = @"Cancel All",
AcceptTypes = new[] { typeof(ProgressNotification) },
},
AcceptTypes = new[] { typeof(ProgressNotification) }
}
}
}
}
@ -70,47 +103,59 @@ namespace osu.Game.Overlays
};
}
private int totalCount => sections.Select(c => c.DisplayedCount).Sum();
private int unreadCount => sections.Select(c => c.UnreadCount).Sum();
public readonly BindableInt UnreadCount = new BindableInt();
private int runningDepth;
private void notificationClosed()
{
// hide ourselves if all notifications have been dismissed.
if (sections.Select(c => c.DisplayedCount).Sum() == 0)
State = Visibility.Hidden;
}
public void Post(Notification notification)
{
Schedule(() =>
{
State = Visibility.Visible;
++runningDepth;
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
notification.Closed += notificationClosed;
var hasCompletionTarget = notification as IHasCompletionTarget;
if (hasCompletionTarget != null)
hasCompletionTarget.CompletionTarget = Post;
var ourType = notification.GetType();
sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification);
// hide ourselves if all notifications have been dismissed.
if (totalCount == 0)
State = Visibility.Hidden;
});
updateCounts();
}
private readonly Scheduler postScheduler = new Scheduler();
private bool processingPosts = true;
public void Post(Notification notification) => postScheduler.Add(() =>
{
++runningDepth;
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
notification.Closed += notificationClosed;
var hasCompletionTarget = notification as IHasCompletionTarget;
if (hasCompletionTarget != null)
hasCompletionTarget.CompletionTarget = Post;
var ourType = notification.GetType();
sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification);
updateCounts();
});
protected override void Update()
{
base.Update();
if (processingPosts)
postScheduler.Update();
}
protected override void PopIn()
{
base.PopIn();
scrollContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(1, TRANSITION_LENGTH / 2);
}
private void markAllRead()
{
sections.Children.ForEach(s => s.MarkAllRead());
this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint);
}
protected override void PopOut()
@ -120,7 +165,26 @@ namespace osu.Game.Overlays
markAllRead();
this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH / 2);
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
}
private void updateCounts()
{
UnreadCount.Value = unreadCount;
}
private void markAllRead()
{
sections.Children.ForEach(s => s.MarkAllRead());
updateCounts();
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
}
}

View File

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

View File

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