1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:33:30 +08:00

Merge master into netstandard

This commit is contained in:
smoogipoo 2018-01-05 00:42:48 +09:00
parent a8ade26147
commit 36e9232472
141 changed files with 1555 additions and 660 deletions

36
COMPILING.md Normal file
View File

@ -0,0 +1,36 @@
# Linux
### 1. Requirements:
Mono >= 5.4.0 (>= 5.8.0 recommended)
Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release.
NuGet >= 4.4.0
msbuild
git
### 2. Cloning project
Clone the entire repository with submodules using
```
git clone https://github.com/ppy/osu --recursive
```
Then restore NuGet packages from the repository
```
nuget restore
```
### 3. Compiling
Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`.
### 4. Optimizing
If you want additional performance you can change build type to Release with
```
msbuild -p:Configuration=Release
```
Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running
```
mono --aot ./osu\!.exe
```
### 5. Troubleshooting
You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run
```
nuget
sudo nuget update -self
```
**Warning** NuGet creates few config files when it's run for the first time.
Do not run NuGet as root on the first run or you might run into very peculiar issues.

View File

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

View File

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

View File

@ -118,7 +118,7 @@ namespace osu.Desktop.Overlays
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion))
Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000);
notificationOverlay.Post(new UpdateCompleteNotification(version));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania.Mods
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 abstract int KeyCount { get; }
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.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
private readonly Slider slider;
private readonly Box follow;
public readonly Box FollowCircle;
private readonly Box ball;
public SliderBall(Slider slider)
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[]
{
follow = new Box
FollowCircle = new Box
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@ -101,11 +101,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos);
public override void ClearTransforms(bool propagateChildren = false, string targetMember = null)
public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
{
// Consider the case of rewinding - children's transforms are handled internally, so propagating down
// any further will cause weirdness with the Tracking bool below. Let's not propagate further at this point.
base.ClearTransforms(false, targetMember);
base.ClearTransformsAfter(time, false, targetMember);
}
private bool tracking;
@ -118,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
tracking = value;
follow.ScaleTo(tracking ? 2.8f : 1, 300, Easing.OutQuint);
follow.FadeTo(tracking ? 0.2f : 0, 300, Easing.OutQuint);
FollowCircle.ScaleTo(tracking ? 2.8f : 1, 300, Easing.OutQuint);
FollowCircle.FadeTo(tracking ? 0.2f : 0, 300, Easing.OutQuint);
}
}
@ -129,11 +129,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
base.Update();
// Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
Tracking = canCurrentlyTrack
&& lastState != null
&& base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
&& ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
if (Time.Current < slider.EndTime)
{
// Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
Tracking = canCurrentlyTrack
&& lastState != null
&& base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
&& ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
}
}
public void UpdateProgress(double progress, int repeat)
@ -141,4 +144,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Position = slider.Curve.PositionAt(progress);
}
}
}
}

View File

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

View File

@ -0,0 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Osu.Objects
{
public interface ISliderProgress
{
void UpdateProgress(double progress, int repeat);
}
}

View File

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

View File

@ -151,28 +151,22 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createRepeatPoints()
{
var length = Curve.Distance;
var repeatPointDistance = Math.Min(Distance, length);
var repeatDuration = length / Velocity;
var repeatDuration = Distance / Velocity;
for (var repeat = 1; repeat < RepeatCount; repeat++)
{
for (var d = repeatPointDistance; d <= length; d += repeatPointDistance)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var distanceProgress = d / length;
var repeatStartTime = StartTime + repeat * repeatDuration;
AddNested(new RepeatPoint
{
RepeatIndex = repeat,
StartTime = repeatStartTime,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeat])
});
}
AddNested(new RepeatPoint
{
RepeatIndex = repeat,
StartTime = repeatStartTime,
Position = Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeat])
});
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,117 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using 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
{
[Ignore("getting CI working")]
public class TestCaseHitCircle : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseHitCircle()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Miss Big Single", () => testSingle(2));
AddStep("Miss Medium Single", () => testSingle(5));
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 testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
var circle = new HitCircle
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new TestDrawableHitCircle(circle, auto)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
};
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);
}
private void testStream(float circleSize, bool auto = false)
{
Vector2 pos = new Vector2(-250, 0);
for (int i = 0; i <= 1000; i += 100)
{
testSingle(circleSize, auto, i, pos);
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

@ -1,129 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
[Ignore("getting CI working")]
public class TestCaseHitObjects : OsuTestCase
{
private FramedClock framedClock;
private bool auto;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var rateAdjustClock = new StopwatchClock(true);
framedClock = new FramedClock(rateAdjustClock);
AddStep(@"circles", () => loadHitobjects(HitObjectType.Circle));
AddStep(@"slider", () => loadHitobjects(HitObjectType.Slider));
AddStep(@"spinner", () => loadHitobjects(HitObjectType.Spinner));
AddToggleStep("Auto", state => { auto = state; loadHitobjects(mode); });
AddSliderStep("Playback speed", 0.0, 2.0, 0.5, v => rateAdjustClock.Rate = v);
framedClock.ProcessFrame();
var clockAdjustContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = framedClock,
Children = new[]
{
playfieldContainer = new OsuInputManager(rulesets.GetRuleset(0)) { RelativeSizeAxes = Axes.Both },
approachContainer = new Container { RelativeSizeAxes = Axes.Both }
}
};
Add(clockAdjustContainer);
}
private HitObjectType mode = HitObjectType.Slider;
private Container playfieldContainer;
private Container approachContainer;
private void loadHitobjects(HitObjectType mode)
{
this.mode = mode;
switch (mode)
{
case HitObjectType.Circle:
const int count = 10;
for (int i = 0; i < count; i++)
{
var h = new HitCircle
{
StartTime = framedClock.CurrentTime + 600 + i * 80,
Position = new Vector2((i - count / 2) * 14),
};
add(new DrawableHitCircle(h));
}
break;
case HitObjectType.Slider:
add(new DrawableSlider(new Slider
{
StartTime = framedClock.CurrentTime + 600,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400,
Position = new Vector2(-200, 0),
Velocity = 1,
TickDistance = 100,
}));
break;
case HitObjectType.Spinner:
add(new DrawableSpinner(new Spinner
{
StartTime = framedClock.CurrentTime + 600,
EndTime = framedClock.CurrentTime + 1600,
Position = new Vector2(0, 0),
}));
break;
}
}
private int depth;
private void add(DrawableOsuHitObject h)
{
h.Anchor = Anchor.Centre;
h.Depth = depth++;
if (auto)
h.State.Value = ArmedState.Hit;
playfieldContainer.Add(h);
var proxyable = h as IDrawableHitObjectWithProxiedApproach;
if (proxyable != null)
approachContainer.Add(proxyable.ProxiedLayer.CreateProxy());
}
private enum HitObjectType
{
Circle,
Slider,
Spinner
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSlider : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderBall),
typeof(SliderBody),
typeof(DrawableSlider),
typeof(DrawableRepeatPoint),
typeof(DrawableOsuHitObject)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseSlider()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Big Single", () => testSimpleBig());
AddStep("Medium Single", () => testSimpleMedium());
AddStep("Small Single", () => testSimpleSmall());
AddStep("Big 1 Repeat", () => testSimpleBig(1));
AddStep("Medium 1 Repeat", () => testSimpleMedium(1));
AddStep("Small 1 Repeat", () => testSimpleSmall(1));
AddStep("Big 2 Repeats", () => testSimpleBig(2));
AddStep("Medium 2 Repeats", () => testSimpleMedium(2));
AddStep("Small 2 Repeats", () => testSimpleSmall(2));
AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps
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 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)
{
repeats++; // The first run through the slider is considered a repeat
var repeatSamples = new List<List<SampleInfo>>();
if (repeats > 1)
{
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
}
var slider = new Slider
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
new Vector2(-(distance / 2), 0),
new Vector2(distance / 2, 0),
},
Distance = distance,
RepeatCount = repeats,
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();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
};
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);
}
}
}

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

@ -0,0 +1,88 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSpinner : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private int depthIndex;
protected readonly List<Mod> Mods = new List<Mod>();
public TestCaseSpinner()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
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 testSingle(float circleSize, bool auto = false)
{
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new TestDrawableSpinner(spinner, auto)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
};
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { 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)
{
var circle = h as HitCircle;
if (circle != null)
if (h is HitCircle circle)
return new DrawableHitCircle(circle);
var slider = h as Slider;
if (slider != null)
if (h is Slider slider)
return new DrawableSlider(slider);
var spinner = h as Spinner;
if (spinner != null)
if (h is Spinner spinner)
return new DrawableSpinner(spinner);
return null;
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Game.Audio;
@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
public class DrumSampleMapping
{
private readonly ControlPointInfo controlPoints;
private readonly Dictionary<SampleControlPoint, DrumSample> mappings = new Dictionary<SampleControlPoint, DrumSample>();
private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio)
{
@ -26,17 +25,17 @@ namespace osu.Game.Rulesets.Taiko.Audio
else
samplePoints = controlPoints.SamplePoints;
foreach (var s in samplePoints.Distinct())
foreach (var s in samplePoints)
{
mappings[s] = new DrumSample
mappings[s.Time] = new DrumSample
{
Centre = s.GetSampleInfo().GetChannel(audio.Sample),
Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample)
Centre = s.GetSampleInfo().GetChannel(audio.Sample, "Taiko"),
Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample, "Taiko")
};
}
}
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time)];
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
public class DrumSample
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable<SampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP);
protected override string SampleNamespace => "Taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
public abstract bool OnPressed(TaikoAction action);

View File

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

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2));
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2));
hitButton = !hitButton;
}
}

View File

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

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Audio;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
[Ignore("getting CI working")]
public class TestCaseInputDrum : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(InputDrum),
typeof(DrumSampleMapping),
typeof(SampleInfo),
typeof(SampleControlPoint)
};
public TestCaseInputDrum()
{
Add(new TaikoInputManager(new RulesetInfo { ID = 1 })
{
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200),
Child = new InputDrum(new ControlPointInfo())
}
});
}
}
}

View File

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

View File

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

View File

@ -152,14 +152,14 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit;
back = centre;
drumSample.Centre.Play();
drumSample.Centre?.Play();
}
else if (action == RimAction)
{
target = rimHit;
back = rim;
drumSample.Rim.Play();
drumSample.Rim?.Play();
}
if (target != null)

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
Assert.AreEqual(Path.Combine("SB", "lyric", "ja-21.png"), sprite.Path);
var animation = background.Elements.ElementAt(12) as StoryboardAnimation;
Assert.NotNull(animation);
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(animation.IsDrawable);
Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
Assert.AreEqual(Anchor.Centre, animation.Origin);
Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
Assert.AreEqual(Path.Combine("SB", "red jitter", "red_0000.jpg"), animation.Path);
Assert.AreEqual(78993, animation.StartTime);
}
}

View File

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

View File

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

View File

@ -15,6 +15,8 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Osu;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
@ -68,6 +70,9 @@ namespace osu.Game.Tests.Visual
case OsuRuleset or:
testOsuMods(or);
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 hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
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);
testMultiMod(doubleTimeMod);
testIncompatibleMods(noFailMod, autoPilotMod);
testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
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)
@ -124,6 +140,12 @@ namespace osu.Game.Tests.Visual
checkNotSelected(mod);
}
private void testUnimplmentedMod(Mod mod)
{
selectNext(mod);
checkNotSelected(mod);
}
private void testIncompatibleMods(Mod modA, Mod modB)
{
selectNext(modA);
@ -169,9 +191,9 @@ namespace osu.Game.Tests.Visual
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)
{

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Overlays;
@ -19,11 +20,12 @@ namespace osu.Game.Tests.Visual
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Notification),
typeof(NotificationSection),
typeof(SimpleNotification),
typeof(ProgressNotification),
typeof(ProgressCompletionNotification),
typeof(SimpleNotification),
typeof(IHasCompletionTarget),
typeof(Notification)
};
public TestCaseNotificationOverlay()
@ -40,17 +42,44 @@ namespace osu.Game.Tests.Visual
Content.Add(displayedCount);
void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state);
void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; };
AddStep(@"toggle", manager.ToggleVisibility);
setState(Visibility.Visible);
AddStep(@"simple #1", sendHelloNotification);
AddStep(@"simple #2", sendAmazingNotification);
AddStep(@"progress #1", sendUploadProgress);
AddStep(@"progress #2", sendDownloadProgress);
AddStep(@"barrage", () => sendBarrage());
checkProgressingCount(2);
setState(Visibility.Hidden);
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
AddWaitStep(5);
checkProgressingCount(0);
AddStep(@"progress #3", sendUploadProgress);
checkProgressingCount(1);
AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33);
AddWaitStep(10);
checkProgressingCount(0);
setState(Visibility.Visible);
//AddStep(@"barrage", () => sendBarrage());
}
private void sendBarrage(int remaining = 100)
private void sendBarrage(int remaining = 10)
{
switch (RNG.Next(0, 4))
{
@ -80,7 +109,7 @@ namespace osu.Game.Tests.Visual
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
{
var p = progressingNotifications.FirstOrDefault(n => n.IsAlive && n.State == ProgressNotificationState.Queued);
var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued);
if (p != null)
p.State = ProgressNotificationState.Active;
}
@ -88,7 +117,7 @@ namespace osu.Game.Tests.Visual
foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
{
if (n.Progress < 1)
n.Progress += (float)(Time.Elapsed / 2000) * RNG.NextSingle();
n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
else
n.State = ProgressNotificationState.Completed;
}
@ -125,5 +154,11 @@ namespace osu.Game.Tests.Visual
{
manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
}
private void sendManyNotifications()
{
for (int i = 0; i < 10; i++)
manager.Post(new SimpleNotification { Text = @"Spam incoming!!" });
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -266,6 +266,6 @@ namespace osu.Game.Beatmaps.Formats
throw new InvalidDataException($@"Unknown origin: {value}");
}
private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"'));
private string cleanFilename(string path) => FileSafety.PathSanitise(path.Trim('\"'));
}
}

View File

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

View File

@ -5,6 +5,8 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
namespace osu.Game.Graphics.Containers
{
@ -22,6 +24,48 @@ namespace osu.Game.Graphics.Containers
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)
{
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)
{
string key = kvp.Key;
switch (key)
HitResult newKey;
switch (kvp.Key)
{
case @"count_300":
key = @"300";
newKey = HitResult.Great;
break;
case @"count_100":
key = @"100";
newKey = HitResult.Good;
break;
case @"count_50":
key = @"50";
newKey = HitResult.Meh;
break;
case @"count_miss":
key = @"x";
newKey = HitResult.Miss;
break;
default:
continue;
}
Statistics.Add(key, kvp.Value);
Statistics.Add(newKey, kvp.Value);
}
}
}

View File

@ -27,6 +27,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Input.Bindings;
using OpenTK.Graphics;
namespace osu.Game
{
@ -65,6 +66,8 @@ namespace osu.Game
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
public readonly BindableBool ShowOverlays = new BindableBool();
private OsuScreen screenStack;
private VolumeControl volume;
@ -280,6 +283,21 @@ namespace osu.Game
settings.StateChanged += _ => updateScreenOffset();
notifications.StateChanged += _ => updateScreenOffset();
notifications.Enabled.BindTo(ShowOverlays);
ShowOverlays.ValueChanged += show =>
{
//central game screen change logic.
if (!show)
{
hideAllOverlays();
musicController.State = Visibility.Hidden;
Toolbar.State = Visibility.Hidden;
}
else
Toolbar.State = Visibility.Visible;
};
Cursor.State = Visibility.Hidden;
}
@ -314,10 +332,21 @@ namespace osu.Game
}
private Task asyncLoadStream;
private int visibleOverlayCount;
private void loadComponentSingleFile<T>(T d, Action<T> add)
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).
// 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.
@ -361,8 +390,6 @@ namespace osu.Game
public bool OnReleased(GlobalAction action) => false;
public event Action<Screen> ScreenChanged;
private Container mainContent;
private Container overlayContent;
@ -380,29 +407,6 @@ namespace osu.Game
notifications.State = Visibility.Hidden;
}
private void screenChanged(Screen newScreen)
{
currentScreen = newScreen as OsuScreen;
if (currentScreen == null)
{
Exit();
return;
}
//central game screen change logic.
if (!currentScreen.ShowOverlays)
{
hideAllOverlays();
musicController.State = Visibility.Hidden;
Toolbar.State = Visibility.Hidden;
}
else
Toolbar.State = Visibility.Visible;
ScreenChanged?.Invoke(newScreen);
}
protected override bool OnExiting()
{
if (screenStack.ChildScreen == null) return false;
@ -448,15 +452,18 @@ namespace osu.Game
private void screenAdded(Screen newScreen)
{
currentScreen = (OsuScreen)newScreen;
newScreen.ModePushed += screenAdded;
newScreen.Exited += screenRemoved;
screenChanged(newScreen);
}
private void screenRemoved(Screen newScreen)
{
screenChanged(newScreen);
currentScreen = (OsuScreen)newScreen;
if (newScreen == null)
Exit();
}
}
}

View File

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

View File

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

View File

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

View File

@ -241,8 +241,6 @@ namespace osu.Game.Overlays
public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{
//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 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;
@ -42,71 +45,73 @@ namespace osu.Game.Overlays.Mods
// A selected index of -1 means not selected.
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;
bool beforeSelected = Selected;
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
Mod modBefore = SelectedMod ?? Mods[0];
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)
backgroundIcon.Icon = modAfter.Icon;
using (BeginDelayedSequence(mod_switch_duration, true))
{
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
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));
}
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;
public Color4 SelectedColour
{
get
{
return selectedColour;
}
get { return selectedColour; }
set
{
if (value == selectedColour) return;
@ -116,12 +121,10 @@ namespace osu.Game.Overlays.Mods
}
private Mod mod;
public Mod Mod
{
get
{
return mod;
}
get { return mod; }
set
{
mod = value;
@ -147,9 +150,7 @@ namespace osu.Game.Overlays.Mods
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]
private void load(AudioManager audio)
@ -163,31 +164,42 @@ namespace osu.Game.Overlays.Mods
switch (args.Button)
{
case MouseButton.Left:
SelectNext();
SelectNext(1);
break;
case MouseButton.Right:
SelectPrevious();
SelectNext(-1);
break;
}
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();
Action?.Invoke(SelectedMod);
int start = selectedIndex + direction;
// 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()
{
(--SelectedIndex == -1 ? sampleOff : sampleOn).Play();
Action?.Invoke(SelectedMod);
}
public void Deselect()
{
SelectedIndex = -1;
}
public void Deselect() => changeSelectedIndex(-1);
private void displayMod(Mod mod)
{
@ -195,6 +207,7 @@ namespace osu.Game.Overlays.Mods
backgroundIcon.Icon = foregroundIcon.Icon;
foregroundIcon.Icon = mod.Icon;
text.Text = mod.Name;
Colour = mod.HasImplementation ? Color4.White : Color4.Gray;
}
private void createIcons()
@ -204,13 +217,13 @@ namespace osu.Game.Overlays.Mods
{
iconsContainer.AddRange(new[]
{
backgroundIcon = new ModIcon(Mods[1])
backgroundIcon = new PassThroughTooltipModIcon(Mods[1])
{
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
Position = new Vector2(1.5f),
},
foregroundIcon = new ModIcon(Mods[0])
foregroundIcon = new PassThroughTooltipModIcon(Mods[0])
{
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
@ -220,7 +233,7 @@ namespace osu.Game.Overlays.Mods
}
else
{
iconsContainer.Add(foregroundIcon = new ModIcon(Mod)
iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@ -259,5 +272,15 @@ namespace osu.Game.Overlays.Mods
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)
{
SelectedColour = selectedColour,
Action = Action,
SelectionChanged = Action,
};
}).ToArray();
@ -83,26 +83,33 @@ namespace osu.Game.Overlays.Mods
{
var index = Array.IndexOf(ToggleKeys, args.Key);
if (index > -1 && index < buttons.Length)
buttons[index].SelectNext();
buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1);
return base.OnKeyDown(state, args);
}
public void DeselectAll()
{
foreach (ModButton button in buttons)
button.Deselect();
}
public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
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)
{
Mod selected = button.SelectedMod;
if (selected == null) continue;
foreach (Type type in modTypes)
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();
}
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;
foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectTypes(modTypes);
section.DeselectTypes(modTypes, immediate);
}
private void modButtonPressed(Mod selectedMod)
{
if (selectedMod != null)
DeselectTypes(selectedMod.IncompatibleMods);
DeselectTypes(selectedMod.IncompatibleMods, true);
refreshSelectedMods();
}
@ -127,10 +132,6 @@ namespace osu.Game.Overlays.Mods
ranked &= mod.Ranked;
}
// 1.00x
// 1.05x
// 1.20x
MultiplierLabel.Text = $"{multiplier:N2}x";
if (!ranked)
MultiplierLabel.Text += " (Unranked)";

View File

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

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -11,7 +10,9 @@ using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Threading;
namespace osu.Game.Overlays
{
@ -21,6 +22,11 @@ namespace osu.Game.Overlays
public const float TRANSITION_LENGTH = 600;
/// <summary>
/// Whether posted notifications should be processed.
/// </summary>
public readonly BindableBool Enabled = new BindableBool(true);
private FlowContainer<NotificationSection> sections;
/// <summary>
@ -28,6 +34,27 @@ namespace osu.Game.Overlays
/// </summary>
public Func<float> GetToolbarHeight;
public NotificationOverlay()
{
ScheduledDelegate notificationsEnabler = null;
Enabled.ValueChanged += v =>
{
if (!IsLoaded)
{
processingPosts = v;
return;
}
notificationsEnabler?.Cancel();
if (v)
// we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed.
notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, 1000);
else
processingPosts = false;
};
}
[BackgroundDependencyLoader]
private void load()
{
@ -85,14 +112,21 @@ namespace osu.Game.Overlays
private void notificationClosed()
{
// hide ourselves if all notifications have been dismissed.
if (totalCount == 0)
State = Visibility.Hidden;
Schedule(() =>
{
// hide ourselves if all notifications have been dismissed.
if (totalCount == 0)
State = Visibility.Hidden;
});
updateCounts();
}
public void Post(Notification notification) => Schedule(() =>
private readonly Scheduler postScheduler = new Scheduler();
private bool processingPosts = true;
public void Post(Notification notification) => postScheduler.Add(() =>
{
++runningDepth;
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
@ -109,6 +143,13 @@ namespace osu.Game.Overlays
updateCounts();
});
protected override void Update()
{
base.Update();
if (processingPosts)
postScheduler.Update();
}
protected override void PopIn()
{
base.PopIn();

View File

@ -15,7 +15,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Notifications
{
public class NotificationSection : FillFlowContainer
public class NotificationSection : AlwaysUpdateFillFlowContainer<Drawable>
{
private OsuSpriteText titleText;
private OsuSpriteText countText;
@ -33,6 +33,7 @@ namespace osu.Game.Overlays.Notifications
public IEnumerable<Type> AcceptTypes;
private string clearText;
public string ClearText
{
get { return clearText; }
@ -110,7 +111,7 @@ namespace osu.Game.Overlays.Notifications
},
},
},
notifications = new FillFlowContainer<Notification>
notifications = new AlwaysUpdateFillFlowContainer<Notification>
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
@ -159,4 +160,13 @@ namespace osu.Game.Overlays.Notifications
notifications?.Children.ForEach(n => n.Read = true);
}
}
public class AlwaysUpdateFillFlowContainer<T> : FillFlowContainer<T>
where T : Drawable
{
// this is required to ensure correct layout and scheduling on children.
// the layout portion of this is being tracked as a framework issue (https://github.com/ppy/osu-framework/issues/1297).
protected override bool RequiresChildrenUpdate => true;
}
}

View File

@ -95,8 +95,8 @@ namespace osu.Game.Overlays.Notifications
protected virtual void Completed()
{
base.Close();
CompletionTarget?.Invoke(CreateCompletionNotification());
base.Close();
}
public override bool DisplayOnTop => false;

View File

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

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
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Overlays.Profile
{
public class SupporterIcon : CircularContainer
public class SupporterIcon : CircularContainer, IHasTooltip
{
private readonly Box background;
public string TooltipText => "osu!supporter";
public SupporterIcon()
{
Masking = true;

View File

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

View File

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

View File

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

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@ -34,15 +33,6 @@ namespace osu.Game.Overlays
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()
{
FirstWaveColour = OsuColour.Gray(0.4f);

View File

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

View File

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

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

View File

@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// An interface for mods that make general adjustments to difficulty.
/// </summary>
public interface IApplicableToDifficulty
public interface IApplicableToDifficulty : IApplicableMod
{
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>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="HitObject"/>s.
/// </summary>
public interface IApplicableToHitObject<in TObject>
public interface IApplicableToHitObject<in TObject> : IApplicableMod
where TObject : HitObject
{
/// <summary>

View File

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

View File

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

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