1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-20 02:12:57 +08:00

Merge branch 'master' into master

This commit is contained in:
為什麼 2018-01-03 21:55:23 +09:00 committed by GitHub
commit 9ecbb5d604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 1053 additions and 470 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

@ -8,8 +8,11 @@ This is still heavily under development and is not intended for end-user use. Th
# Requirements # Requirements
- A desktop platform which can compile .NET 4.5 (tested on macOS, linux and windows). We recommend using [Visual Studio Code](https://code.visualstudio.com/) (all platforms) or [Visual Studio Community Edition](https://www.visualstudio.com/) (windows only), both of which are free. - A desktop platform that can compile .NET 4.6.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here.
- Make sure you initialise and keep submodules up-to-date.
# Getting Started
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`)
# Contributing # Contributing

@ -1 +1 @@
Subproject commit 2438afea86d0ce1f91b1b2f01bca1cf8afdd4659 Subproject commit 6134dafccb3368dac96d837537325c04b89fb8ee

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {

View File

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

View File

@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public abstract class ManiaKeyMod : Mod public abstract class ManiaKeyMod : Mod
{ {
// TODO: implement using the IApplicable interface. Haven't done so yet because KeyCount isn't even hooked up at the moment.
public override string ShortenedName => Name; public override string ShortenedName => Name;
public abstract int KeyCount { get; } public abstract int KeyCount { get; }
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier

View File

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

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy public class OsuModEasy : ModEasy
{ {
} }
public class OsuModHidden : ModHidden public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
{ {
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
d.ApplyCustomUpdateState += ApplyHiddenState;
d.FadeInDuration = preEmpt * fade_in_duration_multiplier;
}
}
protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject d))
return;
var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration;
var fadeOutDuration = preEmpt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime;
switch (drawable)
{
case DrawableHitCircle circle:
// we don't want to see the approach circle
circle.ApproachCircle.Hide();
// fade out immediately after fade in.
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
circle.FadeOut(fadeOutDuration);
break;
case DrawableSlider slider:
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
{
slider.Body.FadeOut(longFadeDuration, Easing.Out);
// delay a bit less to let the sliderball fade out peacefully instead of having a hard cut
using (slider.BeginDelayedSequence(longFadeDuration - fadeOutDuration, true))
slider.Ball.FadeOut(fadeOutDuration);
}
break;
case DrawableSpinner spinner:
// hide elements we don't care about.
spinner.Disc.Hide();
spinner.Ticks.Hide();
spinner.Background.Hide();
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
{
spinner.FadeOut(fadeOutDuration);
// speed up the end sequence accordingly
switch (state)
{
case ArmedState.Hit:
spinner.ScaleTo(spinner.Scale * 1.2f, fadeOutDuration * 2, Easing.Out);
break;
case ArmedState.Miss:
spinner.ScaleTo(spinner.Scale * 0.8f, fadeOutDuration * 2, Easing.In);
break;
}
spinner.Expire();
}
break;
}
}
} }
public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject> public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints; slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve slider.Curve?.Calculate(); // Recalculate the slider curve
} }
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
} }
public class OsuModSuddenDeath : ModSuddenDeath public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect public class OsuModPerfect : ModPerfect
{ {
} }
public class OsuModSpunOut : Mod public class OsuModSpunOut : Mod

View File

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

View File

@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public const float TIME_PREEMPT = 600; public const float TIME_PREEMPT = 600;
public const float TIME_FADEIN = 400; public const float TIME_FADEIN = 400;
public const float TIME_FADEOUT = 500;
/// <summary>
/// The number of milliseconds used to fade in.
/// </summary>
public virtual double FadeInDuration { get; set; } = TIME_FADEIN;
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT;
protected DrawableOsuHitObject(OsuHitObject hitObject) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected virtual void UpdatePreemptState() protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration);
{
this.FadeIn(TIME_FADEIN);
}
protected virtual void UpdateCurrentState(ArmedState state) protected virtual void UpdateCurrentState(ArmedState state)
{ {

View File

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

View File

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

View File

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

View File

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

View File

@ -13,20 +13,21 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSpinner : DrawableOsuHitObject public class DrawableSpinner : DrawableOsuHitObject
{ {
private readonly Spinner spinner; protected readonly Spinner Spinner;
private readonly SpinnerDisc disc; public readonly SpinnerDisc Disc;
private readonly SpinnerTicks ticks; public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter; private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer; private readonly Container mainContainer;
private readonly SpinnerBackground background; public readonly SpinnerBackground Background;
private readonly Container circleContainer; private readonly Container circleContainer;
private readonly CirclePiece circle; private readonly CirclePiece circle;
private readonly GlowPiece glow; private readonly GlowPiece glow;
@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// we are slightly bigger than our parent, to clip the top and bottom of the circle // we are slightly bigger than our parent, to clip the top and bottom of the circle
Height = 1.3f; Height = 1.3f;
spinner = s; Spinner = s;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -84,20 +85,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
background = new SpinnerBackground Background = new SpinnerBackground
{ {
Alpha = 0.6f, Alpha = 0.6f,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
disc = new SpinnerDisc(spinner) Disc = new SpinnerDisc(Spinner)
{ {
Scale = Vector2.Zero, Scale = Vector2.Zero,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
circleContainer.CreateProxy(), circleContainer.CreateProxy(),
ticks = new SpinnerTicks Ticks = new SpinnerTicks
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -114,28 +115,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
} }
public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (Time.Current < HitObject.StartTime) return; if (Time.Current < HitObject.StartTime) return;
if (Progress >= 1 && !disc.Complete) if (Progress >= 1 && !Disc.Complete)
{ {
disc.Complete = true; Disc.Complete = true;
const float duration = 200; const float duration = 200;
disc.FadeAccent(completeColour, duration); Disc.FadeAccent(completeColour, duration);
background.FadeAccent(completeColour, duration); Background.FadeAccent(completeColour, duration);
background.FadeOut(duration); Background.FadeOut(duration);
circle.FadeColour(completeColour, duration); circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration);
} }
if (!userTriggered && Time.Current >= spinner.EndTime) if (!userTriggered && Time.Current >= Spinner.EndTime)
{ {
if (Progress >= 1) if (Progress >= 1)
AddJudgement(new OsuJudgement { Result = HitResult.Great }); AddJudgement(new OsuJudgement { Result = HitResult.Great });
@ -143,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement { Result = HitResult.Good }); AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (Progress > .75) else if (Progress > .75)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); AddJudgement(new OsuJudgement { Result = HitResult.Meh });
else if (Time.Current >= spinner.EndTime) else if (Time.Current >= Spinner.EndTime)
AddJudgement(new OsuJudgement { Result = HitResult.Miss }); AddJudgement(new OsuJudgement { Result = HitResult.Miss });
} }
} }
@ -153,20 +154,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
normalColour = baseColour; normalColour = baseColour;
background.AccentColour = normalColour; Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f); completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = fillColour; Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark; circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark; glow.Colour = colours.BlueDark;
} }
protected override void Update() protected override void Update()
{ {
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && disc.Tracking) if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(TIME_FADEIN); spmCounter.FadeIn(FadeInDuration);
base.Update(); base.Update();
} }
@ -175,36 +176,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
circle.Rotation = disc.Rotation; circle.Rotation = Disc.Rotation;
ticks.Rotation = disc.Rotation; Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute); spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
symbol.RotateTo(disc.Rotation / 2, 500, Easing.OutQuint); symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
} }
protected override void UpdatePreemptState() protected override void UpdatePreemptState()
{ {
base.UpdatePreemptState(); base.UpdatePreemptState();
circleContainer.ScaleTo(spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); circleContainer.ScaleTo(Spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
disc.RotateTo(-720); Disc.RotateTo(-720);
symbol.RotateTo(-720); symbol.RotateTo(-720);
mainContainer mainContainer
.ScaleTo(0) .ScaleTo(0)
.ScaleTo(spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint)
.Then() .Then()
.ScaleTo(1, 500, Easing.OutQuint); .ScaleTo(1, 500, Easing.OutQuint);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
var sequence = this.Delay(spinner.Duration).FadeOut(160); var sequence = this.Delay(Spinner.Duration).FadeOut(160);
switch (state) switch (state)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,59 +11,106 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseHitCircle : OsuTestCase public class TestCaseHitCircle : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool auto;
private int depthIndex; private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseHitCircle() public TestCaseHitCircle()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle()); AddStep("Miss Big Single", () => testSingle(2));
AddStep("Stream", addStream); AddStep("Miss Medium Single", () => testSingle(5));
AddToggleStep("Auto", v => auto = v); AddStep("Miss Small Single", () => testSingle(7));
AddStep("Hit Big Single", () => testSingle(2, true));
AddStep("Hit Medium Single", () => testSingle(5, true));
AddStep("Hit Small Single", () => testSingle(7, true));
AddStep("Miss Big Stream", () => testStream(2));
AddStep("Miss Medium Stream", () => testStream(5));
AddStep("Miss Small Stream", () => testStream(7));
AddStep("Hit Big Stream", () => testStream(2, true));
AddStep("Hit Medium Stream", () => testStream(5, true));
AddStep("Hit Small Stream", () => testStream(7, true));
} }
private void addSingle(double timeOffset = 0, Vector2? positionOffset = null) private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{ {
positionOffset = positionOffset ?? Vector2.Zero; positionOffset = positionOffset ?? Vector2.Zero;
var circle = new HitCircle var circle = new HitCircle
{ {
StartTime = Time.Current + 1000 + timeOffset, StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
}; };
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 }); circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableHitCircle(circle) var drawable = new TestDrawableHitCircle(circle, auto)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Depth = depthIndex++ Depth = depthIndex++
}; };
if (auto) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
drawable.State.Value = ArmedState.Hit; mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable); Add(drawable);
} }
private void addStream() private void testStream(float circleSize, bool auto = false)
{ {
Vector2 pos = Vector2.Zero; Vector2 pos = new Vector2(-250, 0);
for (int i = 0; i <= 1000; i += 100) for (int i = 0; i <= 1000; i += 100)
{ {
addSingle(i, pos); testSingle(circleSize, auto, i, pos);
pos += new Vector2(10); pos.X += 50;
}
}
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)
{
// force success
AddJudgement(new OsuJudgement
{
Result = HitResult.Great
});
State.Value = ArmedState.Hit;
}
else
base.CheckForJudgements(userTriggered, timeOffset);
} }
} }
} }

View File

@ -0,0 +1,22 @@
// 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.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseHitCircleHidden : TestCaseHitCircle
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
public TestCaseHitCircleHidden()
{
Mods.Add(new OsuModHidden());
}
}
}

View File

@ -13,118 +13,140 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseSlider : OsuTestCase public class TestCaseSlider : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableSlider) }; public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderBall),
typeof(SliderBody),
typeof(DrawableSlider),
typeof(DrawableRepeatPoint),
typeof(DrawableOsuHitObject)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private double speedMultiplier = 2;
private double sliderMultiplier = 2;
private int depthIndex; private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseSlider() public TestCaseSlider()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle()); AddStep("Big Single", () => testSimpleBig());
AddStep("Repeated (1)", () => addRepeated(1)); AddStep("Medium Single", () => testSimpleMedium());
AddStep("Repeated (2)", () => addRepeated(2)); AddStep("Small Single", () => testSimpleSmall());
AddStep("Repeated (3)", () => addRepeated(3)); AddStep("Big 1 Repeat", () => testSimpleBig(1));
AddStep("Repeated (4)", () => addRepeated(4)); AddStep("Medium 1 Repeat", () => testSimpleMedium(1));
AddStep("Stream", addStream); AddStep("Small 1 Repeat", () => testSimpleSmall(1));
AddStep("Big 2 Repeats", () => testSimpleBig(2));
AddStep("Medium 2 Repeats", () => testSimpleMedium(2));
AddStep("Small 2 Repeats", () => testSimpleSmall(2));
AddSliderStep("SpeedMultiplier", 0.01, 10, 2, s => speedMultiplier = s); AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps
AddSliderStep("SliderMultiplier", 0.01, 10, 2, s => sliderMultiplier = s); AddStep("Slow Short Slider", () => testShortSlowSpeed());
AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1));
AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2));
AddStep("Fast Slider", () => testHighSpeed());
AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1));
AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2));
AddStep("Fast Short Slider", () => testShortHighSpeed());
AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1));
AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2));
AddStep("Perfect Curve", testCurve);
// TODO more curve types?
} }
private void addSingle(double timeOffset = 0, Vector2? positionOffset = null) private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
private void testSlowSpeed() => createSlider(speedMultiplier: 0.5);
private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2)
{ {
positionOffset = positionOffset ?? Vector2.Zero; repeats++; // The first run through the slider is considered a repeat
var slider = new Slider
{
StartTime = Time.Current + 1000 + timeOffset,
Position = new Vector2(-200, 0) + positionOffset.Value,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0) + positionOffset.Value,
new Vector2(400, 0) + positionOffset.Value,
},
Distance = 400,
};
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = 0
};
slider.ApplyDefaults(cpi, difficulty);
Add(new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
});
}
private void addRepeated(int repeats)
{
// The first run through the slider is considered a repeat
repeats++;
var repeatSamples = new List<List<SampleInfo>>(); var repeatSamples = new List<List<SampleInfo>>();
for (int i = 0; i < repeats; i++) if (repeats > 1)
repeatSamples.Add(new List<SampleInfo>()); {
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
}
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0), Position = new Vector2(-(distance / 2), 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
new Vector2(-200, 0), new Vector2(-(distance / 2), 0),
new Vector2(400, 0), new Vector2(distance / 2, 0),
}, },
Distance = 400, Distance = distance,
RepeatCount = repeats, RepeatCount = repeats,
RepeatSamples = repeatSamples RepeatSamples = repeatSamples
}; };
addSlider(slider, circleSize, speedMultiplier);
}
private void testCurve()
{
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(0, 200),
new Vector2(200, 0)
},
Distance = 600
};
addSlider(slider, 2, 3);
}
private void addSlider(Slider slider, float circleSize, double speedMultiplier)
{
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize });
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = 0
};
slider.ApplyDefaults(cpi, difficulty); var drawable = new DrawableSlider(slider)
Add(new DrawableSlider(slider)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Depth = depthIndex++ Depth = depthIndex++
}); };
}
private void addStream() foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
{ mod.ApplyToDrawableHitObjects(new[] { drawable });
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100) Add(drawable);
{
addSingle(i, pos);
pos += new Vector2(10);
}
} }
} }
} }

View File

@ -0,0 +1,22 @@
// 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.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSliderHidden : TestCaseSlider
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
public TestCaseSliderHidden()
{
Mods.Add(new OsuModHidden());
}
}
}

View File

@ -1,13 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -15,31 +20,69 @@ namespace osu.Game.Rulesets.Osu.Tests
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseSpinner : OsuTestCase public class TestCaseSpinner : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private int depthIndex; private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseSpinner() public TestCaseSpinner()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", addSingle); AddStep("Miss Big", () => testSingle(2));
AddStep("Miss Medium", () => testSingle(5));
AddStep("Miss Small", () => testSingle(7));
AddStep("Hit Big", () => testSingle(2, true));
AddStep("Hit Medium", () => testSingle(5, true));
AddStep("Hit Small", () => testSingle(7, true));
} }
private void addSingle() private void testSingle(float circleSize, bool auto = false)
{ {
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 }); spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableSpinner(spinner) var drawable = new TestDrawableSpinner(spinner, auto)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Depth = depthIndex++ Depth = depthIndex++
}; };
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable); Add(drawable);
} }
private class TestDrawableSpinner : DrawableSpinner
{
private bool auto;
public TestDrawableSpinner(Spinner s, bool auto) : base(s)
{
this.auto = auto;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1)
{
// force completion only once to not break human interaction
Disc.RotationAbsolute = Spinner.SpinsRequired * 360;
auto = false;
}
base.CheckForJudgements(userTriggered, timeOffset);
}
}
} }
} }

View File

@ -0,0 +1,22 @@
// 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.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSpinnerHidden : TestCaseSpinner
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
public TestCaseSpinnerHidden()
{
Mods.Add(new OsuModHidden());
}
}
}

View File

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

View File

@ -88,9 +88,12 @@
<Compile Include="OsuInputManager.cs" /> <Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" /> <Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" /> <Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCaseHitCircleHidden.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" /> <Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseSlider.cs" /> <Compile Include="Tests\TestCaseSlider.cs" />
<Compile Include="Tests\TestCaseSliderHidden.cs" />
<Compile Include="Tests\TestCaseSpinner.cs" /> <Compile Include="Tests\TestCaseSpinner.cs" />
<Compile Include="Tests\TestCaseSpinnerHidden.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" /> <Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" /> <Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" /> <Compile Include="UI\OsuSettings.cs" />

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements namespace osu.Game.Rulesets.Taiko.Judgements
{ {

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables

View File

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

View File

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

View File

@ -20,6 +20,7 @@ using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {

View File

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

View File

@ -15,6 +15,8 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -68,6 +70,9 @@ namespace osu.Game.Tests.Visual
case OsuRuleset or: case OsuRuleset or:
testOsuMods(or); testOsuMods(or);
break; break;
case ManiaRuleset mr:
testManiaMods(mr);
break;
} }
} }
} }
@ -80,16 +85,27 @@ namespace osu.Game.Tests.Visual
var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
testSingleMod(noFailMod); testSingleMod(noFailMod);
testMultiMod(doubleTimeMod); testMultiMod(doubleTimeMod);
testIncompatibleMods(noFailMod, autoPilotMod); testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod))); testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
testMultiplierTextUnranked(autoPilotMod);
testUnimplmentedMod(autoPilotMod);
}
private void testManiaMods(ManiaRuleset ruleset)
{
testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
} }
private void testSingleMod(Mod mod) private void testSingleMod(Mod mod)
@ -124,6 +140,12 @@ namespace osu.Game.Tests.Visual
checkNotSelected(mod); checkNotSelected(mod);
} }
private void testUnimplmentedMod(Mod mod)
{
selectNext(mod);
checkNotSelected(mod);
}
private void testIncompatibleMods(Mod modA, Mod modB) private void testIncompatibleMods(Mod modA, Mod modB)
{ {
selectNext(modA); selectNext(modA);
@ -169,9 +191,9 @@ namespace osu.Game.Tests.Visual
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
} }
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext()); private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1));
private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious()); private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1));
private void checkSelected(Mod mod) private void checkSelected(Mod mod)
{ {

View File

@ -51,11 +51,12 @@ namespace osu.Game.Tests.Visual
private class TestSongSelect : PlaySongSelect private class TestSongSelect : PlaySongSelect
{ {
public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BeatmapManager baseManager) private void load(OsuGameBase game)
{ {
TestSongSelect songSelect = null; TestSongSelect songSelect = null;
@ -69,12 +70,16 @@ namespace osu.Game.Tests.Visual
dependencies.Cache(rulesets = new RulesetStore(contextFactory)); dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
{ {
DefaultBeatmap = defaultBeatmap = baseManager.GetWorkingBeatmap(null) DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
}); });
void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () =>
{ {
if (deleteMaps) manager.DeleteAll(); if (deleteMaps)
{
manager.DeleteAll();
game.Beatmap.SetDefault();
}
if (songSelect != null) if (songSelect != null)
{ {
@ -91,6 +96,8 @@ namespace osu.Game.Tests.Visual
AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap);
AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
AddStep("import test maps", () => AddStep("import test maps", () =>
{ {
for (int i = 0; i < 100; i += 10) for (int i = 0; i < 100; i += 10)

View File

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

View File

@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO; using osu.Game.Beatmaps.IO;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Textures;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.IPC; using osu.Game.IPC;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -101,15 +102,26 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public Func<Storage> GetStableStorage { private get; set; } public Func<Storage> GetStableStorage { private get; set; }
private void refreshImportContext()
{
lock (importContextLock)
{
importContext?.Value?.Dispose();
importContext = new Lazy<OsuDbContext>(() =>
{
var c = createContext();
c.Database.AutoTransactionsEnabled = false;
return c;
});
}
}
public BeatmapManager(Storage storage, Func<OsuDbContext> context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) public BeatmapManager(Storage storage, Func<OsuDbContext> context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{ {
createContext = context; createContext = context;
importContext = new Lazy<OsuDbContext>(() =>
{ refreshImportContext();
var c = createContext();
c.Database.AutoTransactionsEnabled = false;
return c;
});
beatmaps = createBeatmapStore(context); beatmaps = createBeatmapStore(context);
files = new FileStore(context, storage); files = new FileStore(context, storage);
@ -174,13 +186,16 @@ namespace osu.Game.Beatmaps
{ {
e = e.InnerException ?? e; e = e.InnerException ?? e;
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})"); Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
refreshImportContext();
} }
} }
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
} }
private readonly Lazy<OsuDbContext> importContext; private readonly object importContextLock = new object();
private Lazy<OsuDbContext> importContext;
/// <summary> /// <summary>
/// Import a beatmap from an <see cref="ArchiveReader"/>. /// Import a beatmap from an <see cref="ArchiveReader"/>.
@ -189,7 +204,7 @@ namespace osu.Game.Beatmaps
public BeatmapSetInfo Import(ArchiveReader archiveReader) public BeatmapSetInfo Import(ArchiveReader archiveReader)
{ {
// let's only allow one concurrent import at a time for now. // let's only allow one concurrent import at a time for now.
lock (importContext) lock (importContextLock)
{ {
var context = importContext.Value; var context = importContext.Value;
@ -314,7 +329,7 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap set to delete.</param> /// <param name="beatmapSet">The beatmap set to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet) public void Delete(BeatmapSetInfo beatmapSet)
{ {
lock (importContext) lock (importContextLock)
{ {
var context = importContext.Value; var context = importContext.Value;
@ -377,7 +392,7 @@ namespace osu.Game.Beatmaps
if (beatmapSet.Protected) if (beatmapSet.Protected)
return; return;
lock (importContext) lock (importContextLock)
{ {
var context = importContext.Value; var context = importContext.Value;
@ -651,7 +666,7 @@ namespace osu.Game.Beatmaps
try try
{ {
return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile)); return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile));
} }
catch catch
{ {

View File

@ -5,8 +5,8 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Graphics.Textures;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
{ {
@ -22,7 +22,6 @@ namespace osu.Game.Graphics.Backgrounds
this.textureName = textureName; this.textureName = textureName;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Depth = float.MaxValue;
Add(Sprite = new Sprite Add(Sprite = new Sprite
{ {
@ -35,7 +34,7 @@ namespace osu.Game.Graphics.Backgrounds
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
if (!string.IsNullOrEmpty(textureName)) if (!string.IsNullOrEmpty(textureName))
Sprite.Texture = textures.Get(textureName); Sprite.Texture = textures.Get(textureName);

View File

@ -5,6 +5,8 @@ using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -22,6 +24,48 @@ namespace osu.Game.Graphics.Containers
StateChanged += onStateChanged; StateChanged += onStateChanged;
} }
/// <summary>
/// Whether mouse input should be blocked screen-wide while this overlay is visible.
/// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through.
/// </summary>
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceiveMouseInputAt(screenSpacePos);
protected override bool OnWheel(InputState state)
{
// always allow wheel to pass through to stuff outside our DrawRectangle.
if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
return false;
return BlockPassThroughMouse;
}
protected override bool OnClick(InputState state)
{
if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
{
State = Visibility.Hidden;
return true;
}
return base.OnClick(state);
}
protected override bool OnDragStart(InputState state)
{
if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
{
State = Visibility.Hidden;
return true;
}
return base.OnDragStart(state);
}
protected override bool OnDrag(InputState state) => State == Visibility.Hidden;
private void onStateChanged(Visibility visibility) private void onStateChanged(Visibility visibility)
{ {
switch (visibility) switch (visibility)

View File

@ -0,0 +1,18 @@
// 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.Textures;
using osu.Framework.IO.Stores;
namespace osu.Game.Graphics.Textures
{
/// <summary>
/// A texture store that bypasses atlasing.
/// </summary>
public class LargeTextureStore : TextureStore
{
public LargeTextureStore(IResourceStore<RawTexture> store = null) : base(store, false)
{
}
}
}

View File

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

View File

@ -27,6 +27,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using OpenTK.Graphics;
namespace osu.Game namespace osu.Game
{ {
@ -284,10 +285,10 @@ namespace osu.Game
notifications.Enabled.BindTo(ShowOverlays); notifications.Enabled.BindTo(ShowOverlays);
ShowOverlays.ValueChanged += visible => ShowOverlays.ValueChanged += show =>
{ {
//central game screen change logic. //central game screen change logic.
if (!visible) if (!show)
{ {
hideAllOverlays(); hideAllOverlays();
musicController.State = Visibility.Hidden; musicController.State = Visibility.Hidden;
@ -331,10 +332,21 @@ namespace osu.Game
} }
private Task asyncLoadStream; private Task asyncLoadStream;
private int visibleOverlayCount;
private void loadComponentSingleFile<T>(T d, Action<T> add) private void loadComponentSingleFile<T>(T d, Action<T> add)
where T : Drawable where T : Drawable
{ {
var focused = d as FocusedOverlayContainer;
if (focused != null)
{
focused.StateChanged += s =>
{
visibleOverlayCount += s == Visibility.Visible ? 1 : -1;
screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
};
}
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
// with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile,
// we could avoid the need for scheduling altogether. // we could avoid the need for scheduling altogether.

View File

@ -18,8 +18,10 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.Textures;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.IO; using osu.Game.IO;
@ -89,6 +91,8 @@ namespace osu.Game
{ {
dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Host));
dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))));
dependencies.Cache(this); dependencies.Cache(this);
dependencies.Cache(LocalConfig); dependencies.Cache(LocalConfig);

View File

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

View File

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

View File

@ -240,8 +240,6 @@ namespace osu.Game.Overlays
public override bool AcceptsFocus => true; public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state) protected override void OnFocus(InputState state)
{ {
//this is necessary as textbox is masked away and therefore can't get focus :( //this is necessary as textbox is masked away and therefore can't get focus :(

View File

@ -32,7 +32,10 @@ namespace osu.Game.Overlays.Mods
private readonly Container<ModIcon> iconsContainer; private readonly Container<ModIcon> iconsContainer;
private SampleChannel sampleOn, sampleOff; private SampleChannel sampleOn, sampleOff;
public Action<Mod> Action; // Passed the selected mod or null if none /// <summary>
/// Fired when the selection changes.
/// </summary>
public Action<Mod> SelectionChanged;
public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty;
@ -42,71 +45,73 @@ namespace osu.Game.Overlays.Mods
// A selected index of -1 means not selected. // A selected index of -1 means not selected.
private int selectedIndex = -1; private int selectedIndex = -1;
protected int SelectedIndex /// <summary>
/// Change the selected mod index of this button.
/// </summary>
/// <param name="newIndex">The new index.</param>
/// <returns>Whether the selection changed.</returns>
private bool changeSelectedIndex(int newIndex)
{ {
get if (newIndex == selectedIndex) return false;
int direction = newIndex < selectedIndex ? -1 : 1;
bool beforeSelected = Selected;
Mod modBefore = SelectedMod ?? Mods[0];
if (newIndex >= Mods.Length)
newIndex = -1;
else if (newIndex < -1)
newIndex = Mods.Length - 1;
if (newIndex >= 0 && !Mods[newIndex].HasImplementation)
return false;
selectedIndex = newIndex;
Mod modAfter = SelectedMod ?? Mods[0];
if (beforeSelected != Selected)
{ {
return selectedIndex; iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
} }
set
if (modBefore != modAfter)
{ {
if (value == selectedIndex) return; const float rotate_angle = 16;
int direction = value < selectedIndex ? -1 : 1; foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
bool beforeSelected = Selected; backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
Mod modBefore = SelectedMod ?? Mods[0]; backgroundIcon.Icon = modAfter.Icon;
using (BeginDelayedSequence(mod_switch_duration, true))
if (value >= Mods.Length)
selectedIndex = -1;
else if (value < -1)
selectedIndex = Mods.Length - 1;
else
selectedIndex = value;
Mod modAfter = SelectedMod ?? Mods[0];
if (beforeSelected != Selected)
{ {
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); foregroundIcon
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); .RotateTo(-rotate_angle * direction)
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
backgroundIcon
.RotateTo(rotate_angle * direction)
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
Schedule(() => displayMod(modAfter));
} }
if (modBefore != modAfter)
{
const float rotate_angle = 16;
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.Icon = modAfter.Icon;
using (BeginDelayedSequence(mod_switch_duration, true))
{
foregroundIcon
.RotateTo(-rotate_angle * direction)
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
backgroundIcon
.RotateTo(rotate_angle * direction)
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
Schedule(() => displayMod(modAfter));
}
}
foregroundIcon.Highlighted = Selected;
} }
foregroundIcon.Highlighted = Selected;
(selectedIndex == -1 ? sampleOff : sampleOn).Play();
SelectionChanged?.Invoke(SelectedMod);
return true;
} }
public bool Selected => SelectedIndex != -1; public bool Selected => selectedIndex != -1;
private Color4 selectedColour; private Color4 selectedColour;
public Color4 SelectedColour public Color4 SelectedColour
{ {
get get { return selectedColour; }
{
return selectedColour;
}
set set
{ {
if (value == selectedColour) return; if (value == selectedColour) return;
@ -116,12 +121,10 @@ namespace osu.Game.Overlays.Mods
} }
private Mod mod; private Mod mod;
public Mod Mod public Mod Mod
{ {
get get { return mod; }
{
return mod;
}
set set
{ {
mod = value; mod = value;
@ -147,9 +150,7 @@ namespace osu.Game.Overlays.Mods
public Mod[] Mods { get; private set; } public Mod[] Mods { get; private set; }
// the mods from Mod, only multiple if Mod is a MultiMod public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex);
public virtual Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
@ -163,31 +164,42 @@ namespace osu.Game.Overlays.Mods
switch (args.Button) switch (args.Button)
{ {
case MouseButton.Left: case MouseButton.Left:
SelectNext(); SelectNext(1);
break; break;
case MouseButton.Right: case MouseButton.Right:
SelectPrevious(); SelectNext(-1);
break; break;
} }
return true; return true;
} }
public void SelectNext() /// <summary>
/// Select the next available mod in a specified direction.
/// </summary>
/// <param name="direction">1 for forwards, -1 for backwards.</param>
public void SelectNext(int direction)
{ {
(++SelectedIndex == Mods.Length ? sampleOff : sampleOn).Play(); int start = selectedIndex + direction;
Action?.Invoke(SelectedMod); // wrap around if we are at an extremity.
if (start >= Mods.Length)
start = -1;
else if (start < -1)
start = Mods.Length - 1;
for (int i = start; i < Mods.Length && i >= 0; i += direction)
{
if (Mods[i].HasImplementation)
{
changeSelectedIndex(i);
return;
}
}
Deselect();
} }
public void SelectPrevious() public void Deselect() => changeSelectedIndex(-1);
{
(--SelectedIndex == -1 ? sampleOff : sampleOn).Play();
Action?.Invoke(SelectedMod);
}
public void Deselect()
{
SelectedIndex = -1;
}
private void displayMod(Mod mod) private void displayMod(Mod mod)
{ {
@ -195,6 +207,7 @@ namespace osu.Game.Overlays.Mods
backgroundIcon.Icon = foregroundIcon.Icon; backgroundIcon.Icon = foregroundIcon.Icon;
foregroundIcon.Icon = mod.Icon; foregroundIcon.Icon = mod.Icon;
text.Text = mod.Name; text.Text = mod.Name;
Colour = mod.HasImplementation ? Color4.White : Color4.Gray;
} }
private void createIcons() private void createIcons()
@ -204,13 +217,13 @@ namespace osu.Game.Overlays.Mods
{ {
iconsContainer.AddRange(new[] iconsContainer.AddRange(new[]
{ {
backgroundIcon = new ModIcon(Mods[1]) backgroundIcon = new PassThroughTooltipModIcon(Mods[1])
{ {
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Position = new Vector2(1.5f), Position = new Vector2(1.5f),
}, },
foregroundIcon = new ModIcon(Mods[0]) foregroundIcon = new PassThroughTooltipModIcon(Mods[0])
{ {
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
@ -220,7 +233,7 @@ namespace osu.Game.Overlays.Mods
} }
else else
{ {
iconsContainer.Add(foregroundIcon = new ModIcon(Mod) iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -259,5 +272,15 @@ namespace osu.Game.Overlays.Mods
Mod = mod; Mod = mod;
} }
private class PassThroughTooltipModIcon : ModIcon
{
public override string TooltipText => null;
public PassThroughTooltipModIcon(Mod mod)
: base(mod)
{
}
}
} }
} }

View File

@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Mods
return new ModButton(m) return new ModButton(m)
{ {
SelectedColour = selectedColour, SelectedColour = selectedColour,
Action = Action, SelectionChanged = Action,
}; };
}).ToArray(); }).ToArray();
@ -83,26 +83,33 @@ namespace osu.Game.Overlays.Mods
{ {
var index = Array.IndexOf(ToggleKeys, args.Key); var index = Array.IndexOf(ToggleKeys, args.Key);
if (index > -1 && index < buttons.Length) if (index > -1 && index < buttons.Length)
buttons[index].SelectNext(); buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1);
return base.OnKeyDown(state, args); return base.OnKeyDown(state, args);
} }
public void DeselectAll() public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
{
foreach (ModButton button in buttons)
button.Deselect();
}
public void DeselectTypes(Type[] modTypes) /// <summary>
/// Deselect one or more mods in this section.
/// </summary>
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
public void DeselectTypes(IEnumerable<Type> modTypes, bool immediate = false)
{ {
int delay = 0;
foreach (var button in buttons) foreach (var button in buttons)
{ {
Mod selected = button.SelectedMod; Mod selected = button.SelectedMod;
if (selected == null) continue; if (selected == null) continue;
foreach (Type type in modTypes) foreach (Type type in modTypes)
if (type.IsInstanceOfType(selected)) if (type.IsInstanceOfType(selected))
button.Deselect(); {
if (immediate)
button.Deselect();
else
Scheduler.AddDelayed(() => button.Deselect(), delay += 50);
}
} }
} }

View File

@ -100,17 +100,22 @@ namespace osu.Game.Overlays.Mods
refreshSelectedMods(); refreshSelectedMods();
} }
public void DeselectTypes(Type[] modTypes) /// <summary>
/// Deselect one or more mods.
/// </summary>
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
public void DeselectTypes(Type[] modTypes, bool immediate = false)
{ {
if (modTypes.Length == 0) return; if (modTypes.Length == 0) return;
foreach (ModSection section in ModSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectTypes(modTypes); section.DeselectTypes(modTypes, immediate);
} }
private void modButtonPressed(Mod selectedMod) private void modButtonPressed(Mod selectedMod)
{ {
if (selectedMod != null) if (selectedMod != null)
DeselectTypes(selectedMod.IncompatibleMods); DeselectTypes(selectedMod.IncompatibleMods, true);
refreshSelectedMods(); refreshSelectedMods();
} }
@ -127,10 +132,6 @@ namespace osu.Game.Overlays.Mods
ranked &= mod.Ranked; ranked &= mod.Ranked;
} }
// 1.00x
// 1.05x
// 1.20x
MultiplierLabel.Text = $"{multiplier:N2}x"; MultiplierLabel.Text = $"{multiplier:N2}x";
if (!ranked) if (!ranked)
MultiplierLabel.Text += " (Unranked)"; MultiplierLabel.Text += " (Unranked)";

View File

@ -65,10 +65,10 @@ namespace osu.Game.Overlays
AlwaysPresent = true; AlwaysPresent = true;
} }
protected override bool OnDragStart(InputState state) => true;
protected override bool OnDrag(InputState state) protected override bool OnDrag(InputState state)
{ {
if (base.OnDrag(state)) return true;
Trace.Assert(state.Mouse.PositionMouseDown != null, "state.Mouse.PositionMouseDown != null"); Trace.Assert(state.Mouse.PositionMouseDown != null, "state.Mouse.PositionMouseDown != null");
Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
@ -77,7 +77,7 @@ namespace osu.Game.Overlays
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
dragContainer.MoveTo(change); dragContainer.MoveTo(change);
return base.OnDrag(state); return true;
} }
protected override bool OnDragEnd(InputState state) protected override bool OnDragEnd(InputState state)

View File

@ -109,7 +109,7 @@ namespace osu.Game.Overlays.Profile
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Y = -48, Y = -48,
}, },
countryFlag = new DrawableFlag(user.Country?.FlagName) countryFlag = new DrawableFlag(user.Country)
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
@ -333,7 +333,7 @@ namespace osu.Game.Overlays.Profile
{ {
infoTextLeft.AddText("from "); infoTextLeft.AddText("from ");
infoTextLeft.AddText(user.Country.FullName, boldItalic); infoTextLeft.AddText(user.Country.FullName, boldItalic);
countryFlag.FlagName = user.Country.FlagName; countryFlag.Country = user.Country;
} }
infoTextLeft.NewParagraph(); infoTextLeft.NewParagraph();

View File

@ -1,20 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK; using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
public class SupporterIcon : CircularContainer public class SupporterIcon : CircularContainer, IHasTooltip
{ {
private readonly Box background; private readonly Box background;
public string TooltipText => "osu!supporter";
public SupporterIcon() public SupporterIcon()
{ {
Masking = true; Masking = true;

View File

@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections
public class AudioSection : SettingsSection public class AudioSection : SettingsSection
{ {
public override string Header => "Audio"; public override string Header => "Audio";
public override FontAwesome Icon => FontAwesome.fa_headphones; public override FontAwesome Icon => FontAwesome.fa_volume_up;
public AudioSection() public AudioSection()
{ {

View File

@ -177,8 +177,6 @@ namespace osu.Game.Overlays
public override bool AcceptsFocus => true; public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state) protected override void OnFocus(InputState state)
{ {
GetContainingInputManager().ChangeFocus(searchTextBox); GetContainingInputManager().ChangeFocus(searchTextBox);

View File

@ -62,10 +62,10 @@ namespace osu.Game.Overlays.Toolbar
new ToolbarChatButton(), new ToolbarChatButton(),
new ToolbarSocialButton(), new ToolbarSocialButton(),
new ToolbarMusicButton(), new ToolbarMusicButton(),
new ToolbarButton //new ToolbarButton
{ //{
Icon = FontAwesome.fa_search // Icon = FontAwesome.fa_search
}, //},
userArea = new ToolbarUserArea(), userArea = new ToolbarUserArea(),
new ToolbarNotificationButton(), new ToolbarNotificationButton(),
} }

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -34,15 +33,6 @@ namespace osu.Game.Overlays
public const float CONTENT_X_MARGIN = 50; public const float CONTENT_X_MARGIN = 50;
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
protected override bool OnClick(InputState state)
{
State = Visibility.Hidden;
return true;
}
public UserProfileOverlay() public UserProfileOverlay()
{ {
FirstWaveColour = OsuColour.Gray(0.4f); FirstWaveColour = OsuColour.Gray(0.4f);

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Judgements namespace osu.Game.Rulesets.Judgements
{ {

View File

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

View File

@ -0,0 +1,16 @@
// 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.Mods
{
/// <summary>
/// Represents a mod which can override (and block) a fail.
/// </summary>
public interface IApplicableFailOverride : IApplicableMod
{
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
bool AllowFail { get; }
}
}

View File

@ -0,0 +1,13 @@
// 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.Mods
{
/// <summary>
/// The base interface for a mod which can be applied in some way.
/// If this is not implemented by a mod, it will not be available for use in-game.
/// </summary>
public interface IApplicableMod
{
}
}

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for mods that make adjustments to the track. /// An interface for mods that make adjustments to the track.
/// </summary> /// </summary>
public interface IApplicableToClock public interface IApplicableToClock : IApplicableMod
{ {
void ApplyToClock(IAdjustableClock clock); void ApplyToClock(IAdjustableClock clock);
} }

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for mods that make general adjustments to difficulty. /// An interface for mods that make general adjustments to difficulty.
/// </summary> /// </summary>
public interface IApplicableToDifficulty public interface IApplicableToDifficulty : IApplicableMod
{ {
void ApplyToDifficulty(BeatmapDifficulty difficulty); void ApplyToDifficulty(BeatmapDifficulty difficulty);
} }

View File

@ -0,0 +1,20 @@
// 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.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="DrawableHitObject"/>s.
/// </summary>
public interface IApplicableToDrawableHitObjects : IApplicableMod
{
/// <summary>
/// Applies this <see cref="IApplicableToDrawableHitObjects"/> to a list of <see cref="DrawableHitObject"/>s.
/// </summary>
/// <param name="drawable">The list of <see cref="DrawableHitObject"/>s to apply to.</param>
void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables);
}
}

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="HitObject"/>s. /// An interface for <see cref="Mod"/>s that can be applied to <see cref="HitObject"/>s.
/// </summary> /// </summary>
public interface IApplicableToHitObject<in TObject> public interface IApplicableToHitObject<in TObject> : IApplicableMod
where TObject : HitObject where TObject : HitObject
{ {
/// <summary> /// <summary>

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="RulesetContainer"/>s. /// An interface for <see cref="Mod"/>s that can be applied to <see cref="RulesetContainer"/>s.
/// </summary> /// </summary>
public interface IApplicableToRulesetContainer<TObject> public interface IApplicableToRulesetContainer<TObject> : IApplicableMod
where TObject : HitObject where TObject : HitObject
{ {
/// <summary> /// <summary>

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for mods that make general adjustments to score processor. /// An interface for mods that make general adjustments to score processor.
/// </summary> /// </summary>
public interface IApplicableToScoreProcessor public interface IApplicableToScoreProcessor : IApplicableMod
{ {
void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); void ApplyToScoreProcessor(ScoreProcessor scoreProcessor);
} }

View File

@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Mods
/// </summary> /// </summary>
public abstract double ScoreMultiplier { get; } public abstract double ScoreMultiplier { get; }
/// <summary>
/// Returns true if this mod is implemented (and playable).
/// </summary>
public virtual bool HasImplementation => this is IApplicableMod;
/// <summary> /// <summary>
/// Returns if this mod is ranked. /// Returns if this mod is ranked.
/// </summary> /// </summary>
@ -50,10 +55,5 @@ namespace osu.Game.Rulesets.Mods
/// The mods this mod cannot be enabled with. /// The mods this mod cannot be enabled with.
/// </summary> /// </summary>
public virtual Type[] IncompatibleMods => new Type[] { }; public virtual Type[] IncompatibleMods => new Type[] { };
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
public virtual bool AllowFail => true;
} }
} }

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
} }
} }
public class ModAutoplay : Mod public class ModAutoplay : Mod, IApplicableFailOverride
{ {
public override string Name => "Autoplay"; public override string Name => "Autoplay";
public override string ShortenedName => "AT"; public override string ShortenedName => "AT";
@ -29,5 +29,6 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "Watch a perfect automated play through the song"; public override string Description => "Watch a perfect automated play through the song";
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 0;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
public bool AllowFail => false;
} }
} }

View File

@ -6,7 +6,7 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public abstract class ModNoFail : Mod public abstract class ModNoFail : Mod, IApplicableFailOverride
{ {
public override string Name => "NoFail"; public override string Name => "NoFail";
public override string ShortenedName => "NF"; public override string ShortenedName => "NF";
@ -20,6 +20,6 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// We never fail, 'yo. /// We never fail, 'yo.
/// </summary> /// </summary>
public override bool AllowFail => false; public bool AllowFail => false;
} }
} }

View File

@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using OpenTK; using OpenTK;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Objects.Drawables
{ {
@ -122,6 +123,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
UpdateState(state); UpdateState(state);
// apply any custom state overrides
ApplyCustomUpdateState?.Invoke(this, state);
if (State == ArmedState.Hit) if (State == ArmedState.Hit)
PlaySamples(); PlaySamples();
}; };
@ -243,9 +247,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
nestedHitObjects.Add(h); nestedHitObjects.Add(h);
} }
/// <summary>
/// Bind to apply a custom state which can override the default implementation.
/// </summary>
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
protected abstract void UpdateState(ArmedState state); protected abstract void UpdateState(ArmedState state);
} }
} }

View File

@ -3,7 +3,7 @@
using System.ComponentModel; using System.ComponentModel;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Scoring
{ {
public enum HitResult public enum HitResult
{ {

View File

@ -40,6 +40,6 @@ namespace osu.Game.Rulesets.Scoring
public DateTimeOffset Date; public DateTimeOffset Date;
public Dictionary<string, object> Statistics = new Dictionary<string, object>(); public Dictionary<HitResult, object> Statistics = new Dictionary<HitResult, object>();
} }
} }

View File

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

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
private readonly ModType type; private readonly ModType type;
public string TooltipText { get; } public virtual string TooltipText { get; }
public ModIcon(Mod mod) public ModIcon(Mod mod)
{ {

View File

@ -260,6 +260,9 @@ namespace osu.Game.Rulesets.UI
} }
Playfield.PostProcess(); Playfield.PostProcess();
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects);
} }
protected override void Update() protected override void Update()

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Threading;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds namespace osu.Game.Screens.Backgrounds
@ -24,16 +25,22 @@ namespace osu.Game.Screens.Backgrounds
private void display(Background newBackground) private void display(Background newBackground)
{ {
current?.FadeOut(800, Easing.OutQuint); current?.FadeOut(800, Easing.InOutSine);
current?.Expire(); current?.Expire();
Add(current = newBackground); Add(current = newBackground);
currentDisplay++;
} }
private ScheduledDelegate nextTask;
public void Next() public void Next()
{ {
currentDisplay++; nextTask?.Cancel();
LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); nextTask = Scheduler.AddDelayed(() =>
{
LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display);
}, 100);
} }
} }
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multiplayer
set set
{ {
host.Text = value.Username; host.Text = value.Username;
flagContainer.Children = new[] { new DrawableFlag(value.Country?.FlagName) { RelativeSizeAxes = Axes.Both } }; flagContainer.Children = new[] { new DrawableFlag(value.Country) { RelativeSizeAxes = Axes.Both } };
} }
} }

View File

@ -7,10 +7,10 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {

View File

@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play
private bool onFail() private bool onFail()
{ {
if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail)) if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false; return false;
decoupledClock.Stop(); decoupledClock.Stop();

View File

@ -23,6 +23,7 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users; using osu.Game.Users;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Extensions;
namespace osu.Game.Screens.Ranking namespace osu.Game.Screens.Ranking
{ {
@ -163,7 +164,7 @@ namespace osu.Game.Screens.Ranking
} }
}; };
statisticsContainer.ChildrenEnumerable = Score.Statistics.Select(s => new DrawableScoreStatistic(s)); statisticsContainer.ChildrenEnumerable = Score.Statistics.OrderByDescending(p => p.Key).Select(s => new DrawableScoreStatistic(s));
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -186,9 +187,9 @@ namespace osu.Game.Screens.Ranking
private class DrawableScoreStatistic : Container private class DrawableScoreStatistic : Container
{ {
private readonly KeyValuePair<string, object> statistic; private readonly KeyValuePair<HitResult, object> statistic;
public DrawableScoreStatistic(KeyValuePair<string, object> statistic) public DrawableScoreStatistic(KeyValuePair<HitResult, object> statistic)
{ {
this.statistic = statistic; this.statistic = statistic;
@ -209,7 +210,7 @@ namespace osu.Game.Screens.Ranking
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
}, },
new OsuSpriteText { new OsuSpriteText {
Text = statistic.Key, Text = statistic.Key.GetDescription(),
Colour = colours.Gray7, Colour = colours.Gray7,
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
Y = 26, Y = 26,
@ -250,16 +251,16 @@ namespace osu.Game.Screens.Ranking
{ {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Text = datetime.ToString("HH:mm"), Text = datetime.ToShortDateString(),
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
}, },
new OsuSpriteText new OsuSpriteText
{ {
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Text = datetime.ToString("yyyy/MM/dd"), Text = datetime.ToShortTimeString(),
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
} }
}; };

View File

@ -229,11 +229,15 @@ namespace osu.Game.Screens.Select
} }
} }
public void SelectNextRandom() /// <summary>
/// Select the next beatmap in the random sequence.
/// </summary>
/// <returns>True if a selection could be made, else False.</returns>
public bool SelectNextRandom()
{ {
var visible = beatmapSets.Where(s => !s.Filtered).ToList(); var visible = beatmapSets.Where(s => !s.Filtered).ToList();
if (!visible.Any()) if (!visible.Any())
return; return false;
if (selectedBeatmap != null) if (selectedBeatmap != null)
{ {
@ -263,6 +267,7 @@ namespace osu.Game.Screens.Select
set = visible.ElementAt(RNG.Next(visible.Count)); set = visible.ElementAt(RNG.Next(visible.Count));
select(set.Beatmaps.Skip(RNG.Next(set.Beatmaps.Count())).FirstOrDefault()); select(set.Beatmaps.Skip(RNG.Next(set.Beatmaps.Count())).FirstOrDefault());
return true;
} }
public void SelectPreviousRandom() public void SelectPreviousRandom()

View File

@ -46,7 +46,7 @@ namespace osu.Game.Screens.Select.Carousel
{ {
if (songSelect != null) if (songSelect != null)
{ {
startRequested = songSelect.Start; startRequested = songSelect.FinaliseSelection;
editRequested = songSelect.Edit; editRequested = songSelect.Edit;
} }

View File

@ -7,6 +7,10 @@ namespace osu.Game.Screens.Select
{ {
protected override bool ShowFooter => false; protected override bool ShowFooter => false;
protected override void Start() => Exit(); protected override bool OnSelectionFinalised()
{
Exit();
return true;
}
} }
} }

View File

@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select
HeaderText = @"You have no beatmaps!"; HeaderText = @"You have no beatmaps!";
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?";
Icon = FontAwesome.fa_trash_o; Icon = FontAwesome.fa_plane;
Buttons = new PopupDialogButton[] Buttons = new PopupDialogButton[]
{ {

View File

@ -109,6 +109,13 @@ namespace osu.Game.Screens.Select.Leaderboards
{ {
if (value == placeholderState) return; if (value == placeholderState) return;
if (value != PlaceholderState.Successful)
{
getScoresRequest?.Cancel();
getScoresRequest = null;
Scores = null;
}
switch (placeholderState = value) switch (placeholderState = value)
{ {
case PlaceholderState.NetworkFailure: case PlaceholderState.NetworkFailure:
@ -211,10 +218,6 @@ namespace osu.Game.Screens.Select.Leaderboards
private void updateScores() private void updateScores()
{ {
getScoresRequest?.Cancel();
getScoresRequest = null;
Scores = null;
if (Scope == LeaderboardScope.Local) if (Scope == LeaderboardScope.Local)
{ {
// TODO: get local scores from wherever here. // TODO: get local scores from wherever here.
@ -234,16 +237,15 @@ namespace osu.Game.Screens.Select.Leaderboards
return; return;
} }
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter)
{ {
loading.Hide();
PlaceholderState = PlaceholderState.NotSupporter; PlaceholderState = PlaceholderState.NotSupporter;
return; return;
} }
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope);
getScoresRequest.Success += r => getScoresRequest.Success += r =>
{ {

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