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

Merge branch 'master' into fix-randomizer

This commit is contained in:
Endrik 2018-01-03 12:25:07 +02:00 committed by GitHub
commit 35af5d1ce6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 601 additions and 301 deletions

View File

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

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre; Origin = Anchor.Centre;
Position = HitObject.StackedPosition; Position = HitObject.StackedPosition;
Scale = new Vector2(HitObject.Scale); Scale = new Vector2(h.Scale);
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSpinner : DrawableOsuHitObject public class DrawableSpinner : DrawableOsuHitObject
{ {
private readonly Spinner spinner; protected readonly Spinner Spinner;
public readonly SpinnerDisc Disc; public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks; public readonly SpinnerTicks Ticks;
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// we are slightly bigger than our parent, to clip the top and bottom of the circle // we are slightly bigger than our parent, to clip the top and bottom of the circle
Height = 1.3f; Height = 1.3f;
spinner = s; Spinner = s;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
Disc = new SpinnerDisc(spinner) Disc = new SpinnerDisc(Spinner)
{ {
Scale = Vector2.Zero, Scale = Vector2.Zero,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
} }
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
glow.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration);
} }
if (!userTriggered && Time.Current >= spinner.EndTime) if (!userTriggered && Time.Current >= Spinner.EndTime)
{ {
if (Progress >= 1) if (Progress >= 1)
AddJudgement(new OsuJudgement { Result = HitResult.Great }); AddJudgement(new OsuJudgement { Result = HitResult.Great });
@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement { Result = HitResult.Good }); AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (Progress > .75) else if (Progress > .75)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); AddJudgement(new OsuJudgement { Result = HitResult.Meh });
else if (Time.Current >= spinner.EndTime) else if (Time.Current >= Spinner.EndTime)
AddJudgement(new OsuJudgement { Result = HitResult.Miss }); AddJudgement(new OsuJudgement { Result = HitResult.Miss });
} }
} }
@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Ticks.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(Disc.RotationAbsolute); spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
@ -190,22 +190,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdatePreemptState(); base.UpdatePreemptState();
circleContainer.ScaleTo(spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); circleContainer.ScaleTo(Spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
Disc.RotateTo(-720); Disc.RotateTo(-720);
symbol.RotateTo(-720); symbol.RotateTo(-720);
mainContainer mainContainer
.ScaleTo(0) .ScaleTo(0)
.ScaleTo(spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint)
.Then() .Then()
.ScaleTo(1, 500, Easing.OutQuint); .ScaleTo(1, 500, Easing.OutQuint);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
var sequence = this.Delay(spinner.Duration).FadeOut(160); var sequence = this.Delay(Spinner.Duration).FadeOut(160);
switch (state) switch (state)
{ {

View File

@ -11,11 +11,13 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
@ -24,33 +26,34 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableHitCircle) typeof(DrawableHitCircle)
}; };
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool auto;
private bool hidden;
private int depthIndex; private int depthIndex;
private int circleSize; protected readonly List<Mod> Mods = new List<Mod>();
private float circleScale = 1;
public TestCaseHitCircle() public TestCaseHitCircle()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => testSingle()); AddStep("Miss Big Single", () => testSingle(2));
AddStep("Stream", testStream); AddStep("Miss Medium Single", () => testSingle(5));
AddToggleStep("Auto", v => auto = v); AddStep("Miss Small Single", () => testSingle(7));
AddToggleStep("Hidden", v => hidden = v); AddStep("Hit Big Single", () => testSingle(2, true));
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); AddStep("Hit Medium Single", () => testSingle(5, true));
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); 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(double timeOffset = 0, Vector2? positionOffset = null) private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{ {
positionOffset = positionOffset ?? Vector2.Zero; positionOffset = positionOffset ?? Vector2.Zero;
@ -66,27 +69,23 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = new TestDrawableHitCircle(circle, auto) var drawable = new TestDrawableHitCircle(circle, auto)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}; };
if (auto) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
drawable.State.Value = ArmedState.Hit; mod.ApplyToDrawableHitObjects(new[] { drawable });
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable); Add(drawable);
} }
private void testStream() private void testStream(float circleSize, bool auto = false)
{ {
Vector2 pos = Vector2.Zero; Vector2 pos = new Vector2(-250, 0);
for (int i = 0; i <= 1000; i += 100) for (int i = 0; i <= 1000; i += 100)
{ {
testSingle(i, pos); testSingle(circleSize, auto, i, pos);
pos += new Vector2(10); pos.X += 50;
} }
} }
@ -103,13 +102,15 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
if (auto && !userTriggered && timeOffset > 0) if (auto && !userTriggered && timeOffset > 0)
{ {
// pretend we really hit it // force success
AddJudgement(new OsuJudgement AddJudgement(new OsuJudgement
{ {
Result = HitObject.ScoreResultForOffset(timeOffset) Result = HitResult.Great
}); });
State.Value = ArmedState.Hit;
} }
base.CheckForJudgements(userTriggered, timeOffset); else
base.CheckForJudgements(userTriggered, timeOffset);
} }
} }
} }

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseHitCircleHidden : TestCaseHitCircle
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
public TestCaseHitCircleHidden()
{
Mods.Add(new OsuModHidden());
}
}
}

View File

@ -13,8 +13,10 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
@ -23,70 +25,94 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(Slider), typeof(SliderBall),
typeof(HitCircle), typeof(SliderBody),
typeof(OsuModHidden),
typeof(DrawableSlider), typeof(DrawableSlider),
typeof(DrawableHitCircle), typeof(DrawableRepeatPoint),
typeof(DrawableSliderTick), typeof(DrawableOsuHitObject)
typeof(DrawableRepeatPoint)
}; };
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool hidden;
private int repeats;
private int depthIndex; private int depthIndex;
private int circleSize; protected readonly List<Mod> Mods = new List<Mod>();
private float circleScale = 1;
private double speedMultiplier = 2;
private double sliderMultiplier = 2;
public TestCaseSlider() public TestCaseSlider()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => testSingle()); AddStep("Big Single", () => testSimpleBig());
AddStep("Stream", testStream); AddStep("Medium Single", () => testSimpleMedium());
AddStep("Repeated", () => testRepeated(repeats)); AddStep("Small Single", () => testSimpleSmall());
AddToggleStep("Hidden", v => hidden = v); AddStep("Big 1 Repeat", () => testSimpleBig(1));
AddSliderStep("Repeats", 1, 10, 1, s => repeats = s); AddStep("Medium 1 Repeat", () => testSimpleMedium(1));
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); AddStep("Small 1 Repeat", () => testSimpleSmall(1));
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); AddStep("Big 2 Repeats", () => testSimpleBig(2));
AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s); AddStep("Medium 2 Repeats", () => testSimpleMedium(2));
AddSliderStep("SliderMultiplier", 0.1, 10, 2, s => sliderMultiplier = s); 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 testSingle(double timeOffset = 0, Vector2? positionOffset = null) private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
private void testSlowSpeed() => createSlider(speedMultiplier: 0.5);
private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2)
{ {
positionOffset = positionOffset ?? Vector2.Zero; repeats++; // The first run through the slider is considered a repeat
var repeatSamples = new List<List<SampleInfo>>();
if (repeats > 1)
{
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
}
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000 + timeOffset, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0) + positionOffset.Value, Position = new Vector2(-(distance / 2), 0),
ComboColour = Color4.LightSeaGreen, ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
new Vector2(-200, 0) + positionOffset.Value, new Vector2(-(distance / 2), 0),
new Vector2(400, 0) + positionOffset.Value, new Vector2(distance / 2, 0),
}, },
Distance = 400 Distance = distance,
RepeatCount = repeats,
RepeatSamples = repeatSamples
}; };
addSlider(slider); addSlider(slider, circleSize, speedMultiplier);
} }
private void testRepeated(int repeats) private void testCurve()
{ {
// The first run through the slider is considered a repeat
repeats++;
var repeatSamples = new List<List<SampleInfo>>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
@ -95,52 +121,32 @@ namespace osu.Game.Rulesets.Osu.Tests
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
new Vector2(-200, 0), new Vector2(-200, 0),
new Vector2(400, 0), new Vector2(0, 200),
new Vector2(200, 0)
}, },
Distance = 400, Distance = 600
RepeatCount = repeats,
RepeatSamples = repeatSamples
}; };
addSlider(slider); addSlider(slider, 2, 3);
} }
private void testStream() private void addSlider(Slider slider, float circleSize, double speedMultiplier)
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
testSingle(i, pos);
pos += new Vector2(10);
}
}
private void addSlider(Slider slider)
{ {
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize });
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = circleSize
};
slider.ApplyDefaults(cpi, difficulty);
var drawable = new DrawableSlider(slider) var drawable = new DrawableSlider(slider)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}; };
if (hidden) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(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

@ -3,15 +3,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -21,46 +22,67 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(Spinner), typeof(SpinnerDisc),
typeof(OsuModHidden), typeof(DrawableSpinner),
typeof(DrawableSpinner) typeof(DrawableOsuHitObject)
}; };
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool hidden;
private int depthIndex; private int depthIndex;
private int circleSize; protected readonly List<Mod> Mods = new List<Mod>();
private float circleScale = 1;
public TestCaseSpinner() public TestCaseSpinner()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", testSingle); AddStep("Miss Big", () => testSingle(2));
AddToggleStep("Hidden", v => hidden = v); AddStep("Miss Medium", () => testSingle(5));
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); AddStep("Miss Small", () => testSingle(7));
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); AddStep("Hit Big", () => testSingle(2, true));
AddStep("Hit Medium", () => testSingle(5, true));
AddStep("Hit Small", () => testSingle(7, true));
} }
private void testSingle() private void testSingle(float circleSize, bool auto = false)
{ {
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableSpinner(spinner) var drawable = new TestDrawableSpinner(spinner, auto)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}; };
if (hidden) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable); Add(drawable);
} }
private class TestDrawableSpinner : DrawableSpinner
{
private bool auto;
public TestDrawableSpinner(Spinner s, bool auto) : base(s)
{
this.auto = auto;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1)
{
// force completion only once to not break human interaction
Disc.RotationAbsolute = Spinner.SpinsRequired * 360;
auto = false;
}
base.CheckForJudgements(userTriggered, timeOffset);
}
}
} }
} }

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSpinnerHidden : TestCaseSpinner
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
public TestCaseSpinnerHidden()
{
Mods.Add(new OsuModHidden());
}
}
}

View File

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

View File

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

View File

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

View File

@ -15,6 +15,15 @@ namespace osu.Game.Tests.Visual
{ {
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Score),
typeof(Results),
typeof(ResultsPage),
typeof(ResultsPageScore),
typeof(ResultsPageRanking)
};
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps) private void load(BeatmapManager beatmaps)
{ {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
namespace osu.Game.Graphics.Textures
{
/// <summary>
/// A texture store that bypasses atlasing.
/// </summary>
public class LargeTextureStore : TextureStore
{
public LargeTextureStore(IResourceStore<RawTexture> store = null) : base(store, false)
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections
public class AudioSection : SettingsSection public class AudioSection : SettingsSection
{ {
public override string Header => "Audio"; public override string Header => "Audio";
public override FontAwesome Icon => FontAwesome.fa_headphones; public override FontAwesome Icon => FontAwesome.fa_volume_up;
public AudioSection() public AudioSection()
{ {
@ -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; public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state) protected override void OnFocus(InputState state)
{ {
GetContainingInputManager().ChangeFocus(searchTextBox); GetContainingInputManager().ChangeFocus(searchTextBox);

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="DrawableHitObject"/>s. /// An interface for <see cref="Mod"/>s that can be applied to <see cref="DrawableHitObject"/>s.
/// </summary> /// </summary>
public interface IApplicableToDrawableHitObjects public interface IApplicableToDrawableHitObjects : IApplicableMod
{ {
/// <summary> /// <summary>
/// Applies this <see cref="IApplicableToDrawableHitObjects"/> to a list of <see cref="DrawableHitObject"/>s. /// Applies this <see cref="IApplicableToDrawableHitObjects"/> to a list of <see cref="DrawableHitObject"/>s.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,16 +251,16 @@ namespace osu.Game.Screens.Ranking
{ {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Text = datetime.ToString("HH:mm"), Text = datetime.ToShortDateString(),
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
}, },
new OsuSpriteText new OsuSpriteText
{ {
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Text = datetime.ToString("yyyy/MM/dd"), Text = datetime.ToShortTimeString(),
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
} }
}; };

View File

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

View File

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

View File

@ -25,6 +25,8 @@ namespace osu.Game.Screens.Select.Options
private readonly Box holder; private readonly Box holder;
private readonly FillFlowContainer<BeatmapOptionsButton> buttonsContainer; private readonly FillFlowContainer<BeatmapOptionsButton> buttonsContainer;
public override bool BlockScreenWideMouse => false;
protected override void PopIn() protected override void PopIn()
{ {
base.PopIn(); base.PopIn();

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.Select
{ {
private OsuScreen player; private OsuScreen player;
private readonly ModSelectOverlay modSelect; private readonly ModSelectOverlay modSelect;
private readonly BeatmapDetailArea beatmapDetails; protected readonly BeatmapDetailArea BeatmapDetails;
private bool removeAutoModOnResume; private bool removeAutoModOnResume;
public PlaySongSelect() public PlaySongSelect()
@ -35,13 +35,13 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
}); });
LeftContent.Add(beatmapDetails = new BeatmapDetailArea LeftContent.Add(BeatmapDetails = new BeatmapDetailArea
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10, Right = 5 }, Padding = new MarginPadding { Top = 10, Right = 5 },
}); });
beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s)); BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
} }
private SampleChannel sampleConfirm; private SampleChannel sampleConfirm;
@ -78,7 +78,7 @@ namespace osu.Game.Screens.Select
beatmap.Mods.BindTo(modSelect.SelectedMods); beatmap.Mods.BindTo(modSelect.SelectedMods);
beatmapDetails.Beatmap = beatmap; BeatmapDetails.Beatmap = beatmap;
if (beatmap.Track != null) if (beatmap.Track != null)
beatmap.Track.Looping = true; beatmap.Track.Looping = true;

View File

@ -449,9 +449,16 @@ namespace osu.Game.Screens.Select
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false) if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false)
{
Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo);
}
else if (Carousel.SelectedBeatmapSet == null) else if (Carousel.SelectedBeatmapSet == null)
Carousel.SelectNextRandom(); {
if (!Carousel.SelectNextRandom())
// in the case random selection failed, we want to trigger selectionChanged
// to show the dummy beatmap (we have nothing else to display).
carouselSelectionChanged(null);
}
} }
private void delete(BeatmapSetInfo beatmap) private void delete(BeatmapSetInfo beatmap)

View File

@ -7,6 +7,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -203,6 +204,8 @@ namespace osu.Game.Tests.Visual
private readonly FillFlowContainer<PerformanceDisplay> scores; private readonly FillFlowContainer<PerformanceDisplay> scores;
private APIAccess api; private APIAccess api;
private readonly Bindable<WorkingBeatmap> currentBeatmap = new Bindable<WorkingBeatmap>();
public PerformanceList() public PerformanceList()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -231,12 +234,15 @@ namespace osu.Game.Tests.Visual
}; };
} }
osuGame.Beatmap.ValueChanged += beatmapChanged; currentBeatmap.ValueChanged += beatmapChanged;
currentBeatmap.BindTo(osuGame.Beatmap);
} }
private GetScoresRequest lastRequest; private GetScoresRequest lastRequest;
private void beatmapChanged(WorkingBeatmap newBeatmap) private void beatmapChanged(WorkingBeatmap newBeatmap)
{ {
if (!IsAlive) return;
lastRequest?.Cancel(); lastRequest?.Cancel();
scores.Clear(); scores.Clear();

View File

@ -267,6 +267,7 @@
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" /> <Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
<Compile Include="Database\DatabaseContextFactory.cs" /> <Compile Include="Database\DatabaseContextFactory.cs" />
<Compile Include="Database\IHasPrimaryKey.cs" /> <Compile Include="Database\IHasPrimaryKey.cs" />
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
<Compile Include="Overlays\Profile\SupporterIcon.cs" /> <Compile Include="Overlays\Profile\SupporterIcon.cs" />
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" /> <Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />
<Compile Include="Graphics\UserInterface\HoverClickSounds.cs" /> <Compile Include="Graphics\UserInterface\HoverClickSounds.cs" />
@ -310,6 +311,8 @@
<Compile Include="Overlays\Profile\Sections\Ranks\DrawableTotalScore.cs" /> <Compile Include="Overlays\Profile\Sections\Ranks\DrawableTotalScore.cs" />
<Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" /> <Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" />
<Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" /> <Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" />
<Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" />
<Compile Include="Rulesets\Mods\IApplicableMod.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" /> <Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Screens\Select\ImportFromStablePopup.cs" /> <Compile Include="Screens\Select\ImportFromStablePopup.cs" />
<Compile Include="Overlays\Settings\SettingsButton.cs" /> <Compile Include="Overlays\Settings\SettingsButton.cs" />