diff --git a/README.md b/README.md
index a1f478e39a..dc36145337 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,9 @@ Clone the repository including submodules
Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
-- From command line using `dotnet run --project osu.Desktop`
+- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance.
+
+Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example
If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build.
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
index 6d8d95e773..355f0603be 100644
--- a/appveyor_deploy.yml
+++ b/appveyor_deploy.yml
@@ -10,8 +10,8 @@ before_build:
- cmd: nuget restore -verbosity quiet
build_script:
- ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1'))
- - appveyor DownloadFile https://puu.sh/A6g5K/4d08705438.enc # signing certificate
- - cmd: appveyor-tools\secure-file -decrypt 4d08705438.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx
+ - appveyor DownloadFile https://www.dropbox.com/s/f7hv069mr5tqi2j/deanherbert.pfx.enc?dl=1 # signing certificate
+ - cmd: appveyor-tools\secure-file -decrypt deanherbert.pfx.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx
- appveyor DownloadFile https://puu.sh/A6g75/fdc6f19b04.enc # deploy configuration
- cd osu-deploy
- nuget restore -verbosity quiet
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 1129969694..96857d6b4f 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -27,9 +27,6 @@ namespace osu.Desktop.Overlays
private NotificationOverlay notificationOverlay;
private GameHost host;
- public override bool HandleKeyboardInput => false;
- public override bool HandleMouseInput => false;
-
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
{
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 180abd7fec..2be352165e 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -27,9 +27,9 @@
-
-
-
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
index 34e07170bd..5e68acde94 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream
{
X = 0.5f - width / 2,
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 51343d9e91..326791f506 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,9 +2,10 @@
-
-
+
+
+
WinExe
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index ab0afb08d7..c7ea29f8c0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -74,42 +74,42 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private void initialiseHyperDash(List objects)
{
- // todo: add difficulty adjust.
- double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
+ List objectWithDroplets = new List();
+ foreach (var currentObject in objects)
+ {
+ if (currentObject is Fruit)
+ objectWithDroplets.Add(currentObject);
+ if (currentObject is JuiceStream)
+ foreach (var currentJuiceElement in currentObject.NestedHitObjects)
+ if (!(currentJuiceElement is TinyDroplet))
+ objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
+ }
+
+ objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+
+ double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
- int objCount = objects.Count;
-
- for (int i = 0; i < objCount - 1; i++)
+ for (int i = 0; i < objectWithDroplets.Count - 1; i++)
{
- CatchHitObject currentObject = objects[i];
-
- // not needed?
- // if (currentObject is TinyDroplet) continue;
-
- CatchHitObject nextObject = objects[i + 1];
-
- // while (nextObject is TinyDroplet)
- // {
- // if (++i == objCount - 1) break;
- // nextObject = objects[i + 1];
- // }
+ CatchHitObject currentObject = objectWithDroplets[i];
+ CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
- double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
+ double timeToNext = nextObject.StartTime - currentObject.StartTime;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
-
- if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
+ float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
+ if (distanceToHyper < 0)
{
currentObject.HyperDashTarget = nextObject;
lastExcess = halfCatcherWidth;
}
else
{
- //currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
- lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
+ currentObject.DistanceToHyperDash = distanceToHyper;
+ lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
}
lastDirection = thisDirection;
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 621fc100c2..0c50dae9fb 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public int IndexInBeatmap { get; set; }
- public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
+ public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4);
public virtual bool NewCombo { get; set; }
@@ -27,7 +27,9 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; }
///
- /// The distance for a fruit to to next hyper if it's not a hyper.
+ /// Difference between the distance to the next object
+ /// and the distance that would have triggered a hyper dash.
+ /// A value close to 0 indicates a difficult jump (for difficulty calculation).
///
public float DistanceToHyperDash { get; set; }
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 0344189af5..82e32d24d2 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public SliderCurve Curve { get; } = new SliderCurve();
- public List ControlPoints
+ public Vector2[] ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index d49be69856..925e7aaac9 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -5,11 +5,13 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -17,13 +19,13 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float BASE_WIDTH = 512;
- protected override Container Content => content;
- private readonly Container content;
-
private readonly CatcherArea catcherArea;
+ protected override bool UserScrollSpeedAdjustment => false;
+
+ protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Constant;
+
public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation)
- : base(BASE_WIDTH)
{
Direction.Value = ScrollingDirection.Down;
@@ -32,27 +34,29 @@ namespace osu.Game.Rulesets.Catch.UI
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
- base.Content.Anchor = Anchor.BottomLeft;
- base.Content.Origin = Anchor.BottomLeft;
+ Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
- base.Content.AddRange(new Drawable[]
+ InternalChild = new PlayfieldAdjustmentContainer
{
- explodingFruitContainer = new Container
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- },
- catcherArea = new CatcherArea(difficulty)
- {
- GetVisualRepresentation = getVisualRepresentation,
- ExplodingFruitTarget = explodingFruitContainer,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.TopLeft,
- },
- content = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- });
+ explodingFruitContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ catcherArea = new CatcherArea(difficulty)
+ {
+ GetVisualRepresentation = getVisualRepresentation,
+ ExplodingFruitTarget = explodingFruitContainer,
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.TopLeft,
+ },
+ HitObjectContainer
+ }
+ };
+
+ VisibleTimeRange.Value = BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
}
public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj);
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 1ac052de4d..bd0fec43a1 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -13,7 +13,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Catch.UI
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate
-
protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h)
{
switch (h)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 4327abb96f..06453ac32d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
- public const float CATCHER_SIZE = 84;
+ public const float CATCHER_SIZE = 100;
protected readonly Catcher MovableCatcher;
@@ -107,6 +107,11 @@ namespace osu.Game.Rulesets.Catch.UI
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
+ public static float GetCatcherSize(BeatmapDifficulty difficulty)
+ {
+ return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
public class Catcher : Container, IKeyBindingHandler
{
///
@@ -407,9 +412,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
public void Explode()
{
- var fruit = caughtFruit.ToArray();
-
- foreach (var f in fruit)
+ foreach (var f in caughtFruit.ToArray())
Explode(f);
}
@@ -422,15 +425,15 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
- caughtFruit.Remove(fruit);
+ if (!caughtFruit.Remove(fruit))
+ // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
+ // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
+ return;
ExplodingFruitTarget.Add(fruit);
}
- fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
- .Then()
- .MoveToY(fruit.Y + 50, 500, Easing.InSine);
-
+ fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs
new file mode 100644
index 0000000000..ad0073ff12
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs
@@ -0,0 +1,42 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class PlayfieldAdjustmentContainer : Container
+ {
+ protected override Container Content => content;
+ private readonly Container content;
+
+ public PlayfieldAdjustmentContainer()
+ {
+ InternalChild = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ FillAspectRatio = 4f / 3,
+ Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
+ };
+ }
+
+ ///
+ /// A which scales its content relative to a target width.
+ ///
+ private class ScalingContainer : Container
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH);
+ Size = Vector2.Divide(Vector2.One, Scale);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 3165f69a6b..bf75ebbff8 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,9 +2,10 @@
-
-
+
+
+
WinExe
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index c15b303048..5d8480d969 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
TargetColumns = (int)Math.Max(1, roundedCircleSize);
else
{
- float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count();
+ float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 010cf962cc..37a8062d75 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -173,26 +173,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects;
- int nextColumn = Random.Next(RandomStart, TotalColumns);
+ int nextColumn = GetRandomColumn();
for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
{
// Find available column
- RunWhile(() => pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
-
+ nextColumn = FindAvailableColumn(nextColumn, pattern, PreviousPattern);
addToPattern(pattern, nextColumn, startTime, EndTime);
}
// This is can't be combined with the above loop due to RNG
for (int i = 0; i < noteCount - usableColumns; i++)
{
- RunWhile(() => pattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
-
+ nextColumn = FindAvailableColumn(nextColumn, pattern);
addToPattern(pattern, nextColumn, startTime, EndTime);
}
@@ -217,23 +209,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
- {
- RunWhile(() => PreviousPattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
- }
+ nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
int lastColumn = nextColumn;
for (int i = 0; i < noteCount; i++)
{
addToPattern(pattern, nextColumn, startTime, startTime);
-
- RunWhile(() => nextColumn == lastColumn, () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
-
+ nextColumn = FindAvailableColumn(nextColumn, validation: c => c != lastColumn);
lastColumn = nextColumn;
startTime += SegmentDuration;
}
@@ -325,7 +307,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (TotalColumns > 2)
addToPattern(pattern, nextColumn, startTime, startTime);
- nextColumn = Random.Next(RandomStart, TotalColumns);
+ nextColumn = GetRandomColumn();
startTime += SegmentDuration;
}
@@ -404,20 +386,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
- {
- RunWhile(() => PreviousPattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
- }
+ nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
for (int i = 0; i < columnRepeat; i++)
{
- RunWhile(() => pattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
-
+ nextColumn = FindAvailableColumn(nextColumn, pattern);
addToPattern(pattern, nextColumn, startTime, EndTime);
startTime += SegmentDuration;
}
@@ -442,17 +415,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
- {
- RunWhile(() => PreviousPattern.ColumnHasObject(holdColumn), () =>
- {
- holdColumn = Random.Next(RandomStart, TotalColumns);
- });
- }
+ holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
// Create the hold note
addToPattern(pattern, holdColumn, startTime, EndTime);
- int nextColumn = Random.Next(RandomStart, TotalColumns);
+ int nextColumn = GetRandomColumn();
int noteCount;
if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0);
@@ -473,11 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
for (int j = 0; j < noteCount; j++)
{
- RunWhile(() => rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn, () =>
- {
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
-
+ nextColumn = FindAvailableColumn(nextColumn, validation: c => c != holdColumn, patterns: rowPattern);
addToPattern(rowPattern, nextColumn, startTime, startTime);
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index eae9a0fc3b..775a4145e6 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -39,34 +39,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, 0, generateHold);
break;
case 8:
- addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
+ addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold);
break;
default:
if (TotalColumns > 0)
- addToPattern(pattern, getNextRandomColumn(0), generateHold);
+ addToPattern(pattern, GetRandomColumn(), generateHold);
break;
}
return pattern;
}
- ///
- /// Picks a random column after a column.
- ///
- /// The starting column.
- /// A random column after .
- private int getNextRandomColumn(int start)
- {
- int nextColumn = Random.Next(start, TotalColumns);
-
- RunWhile(() => PreviousPattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(start, TotalColumns);
- });
-
- return nextColumn;
- }
-
///
/// Constructs and adds a note to a pattern.
///
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 5860480a91..da1dd62cf5 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -25,9 +25,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
PatternType lastStair, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
- if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
- if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
-
StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
@@ -234,22 +231,27 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i < noteCount; i++)
{
- RunWhile(() => pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking, () =>
- {
- if (convertType.HasFlag(PatternType.Gathered))
- {
- nextColumn++;
- if (nextColumn == TotalColumns)
- nextColumn = RandomStart;
- }
- else
- nextColumn = Random.Next(RandomStart, TotalColumns);
- });
+ nextColumn = allowStacking
+ ? FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: pattern)
+ : FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: new[] { pattern, PreviousPattern });
addToPattern(pattern, nextColumn);
}
return pattern;
+
+ int getNextColumn(int last)
+ {
+ if (convertType.HasFlag(PatternType.Gathered))
+ {
+ last++;
+ if (last == TotalColumns)
+ last = RandomStart;
+ }
+ else
+ last = GetRandomColumn();
+ return last;
+ }
}
///
@@ -295,13 +297,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
- int nextColumn = Random.Next(RandomStart, columnLimit);
+ int nextColumn = GetRandomColumn(upperBound: columnLimit);
for (int i = 0; i < noteCount; i++)
{
- RunWhile(() => pattern.ColumnHasObject(nextColumn), () =>
- {
- nextColumn = Random.Next(RandomStart, columnLimit);
- });
+ nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern);
// Add normal note
addToPattern(pattern, nextColumn);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 55081e5822..7bd39adb45 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using JetBrains.Annotations;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
@@ -90,6 +91,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
private double? conversionDifficulty;
+
///
/// A difficulty factor used for various conversion methods from osu!stable.
///
@@ -110,11 +112,88 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
+ conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
}
}
+
+ ///
+ /// Finds a new column in which a can be placed.
+ /// This uses to pick the next candidate column.
+ ///
+ /// The initial column to test. This may be returned if it is already a valid column.
+ /// A list of patterns for which the validity of a column should be checked against.
+ /// A column is not a valid candidate if a occupies the same column in any of the patterns.
+ /// A column for which there are no s in any of occupying the same column.
+ /// If there are no valid candidate columns.
+ protected int FindAvailableColumn(int initialColumn, params Pattern[] patterns)
+ => FindAvailableColumn(initialColumn, null, patterns: patterns);
+
+ ///
+ /// Finds a new column in which a can be placed.
+ ///
+ /// The initial column to test. This may be returned if it is already a valid column.
+ /// A function to retrieve the next column. If null, a randomisation scheme will be used.
+ /// A function to perform additional validation checks to determine if a column is a valid candidate for a .
+ /// The minimum column index. If null, is used.
+ /// The maximum column index. If null, is used.
+ /// A list of patterns for which the validity of a column should be checked against.
+ /// A column is not a valid candidate if a occupies the same column in any of the patterns.
+ /// A column which has passed the check and for which there are no
+ /// s in any of occupying the same column.
+ /// If there are no valid candidate columns.
+ protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func nextColumn = null, [InstantHandle] Func validation = null,
+ params Pattern[] patterns)
+ {
+ lowerBound = lowerBound ?? RandomStart;
+ upperBound = upperBound ?? TotalColumns;
+ nextColumn = nextColumn ?? (_ => GetRandomColumn(lowerBound, upperBound));
+
+ // Check for the initial column
+ if (isValid(initialColumn))
+ return initialColumn;
+
+ // Ensure that we have at least one free column, so that an endless loop is avoided
+ bool hasValidColumns = false;
+ for (int i = lowerBound.Value; i < upperBound.Value; i++)
+ {
+ hasValidColumns = isValid(i);
+ if (hasValidColumns)
+ break;
+ }
+
+ if (!hasValidColumns)
+ throw new NotEnoughColumnsException();
+
+ // Iterate until a valid column is found. This is a random iteration in the default case.
+ do
+ {
+ initialColumn = nextColumn(initialColumn);
+ } while (!isValid(initialColumn));
+
+ return initialColumn;
+
+ bool isValid(int column) => validation?.Invoke(column) != false && !patterns.Any(p => p.ColumnHasObject(column));
+ }
+
+ ///
+ /// Returns a random column index in the range [, ).
+ ///
+ /// The minimum column index. If null, is used.
+ /// The maximum column index. If null, is used.
+ protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
+
+ ///
+ /// Occurs when mania conversion is stuck in an infinite loop unable to find columns to place new hitobjects in.
+ ///
+ public class NotEnoughColumnsException : Exception
+ {
+ public NotEnoughColumnsException()
+ : base("There were not enough columns to complete conversion.")
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
index e51cbcdc60..a42d57cdd1 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
@@ -3,9 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using JetBrains.Annotations;
-using osu.Framework.Logging;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@@ -15,14 +12,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
///
internal abstract class PatternGenerator
{
- ///
- /// An arbitrary maximum amount of iterations to perform in .
- /// The specific value is not super important - enough such that no false-positives occur.
- ///
- /// /b/933228 requires at least 23 iterations.
- ///
- private const int max_rng_iterations = 30;
-
///
/// The last pattern.
///
@@ -53,44 +42,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
TotalColumns = Beatmap.TotalColumns;
}
- protected void RunWhile([InstantHandle] Func condition, Action action)
- {
- int iterations = 0;
-
- while (condition())
- {
- if (iterations++ >= max_rng_iterations)
- {
- // log an error but don't throw. we want to continue execution.
- Logger.Error(new ExceededAllowedIterationsException(new StackTrace(0)),
- "Conversion encountered errors. The beatmap may not be correctly converted.");
- return;
- }
-
- action();
- }
- }
-
///
/// Generates the patterns for , each filled with hit objects.
///
/// The s containing the hit objects.
public abstract IEnumerable Generate();
-
- ///
- /// Denotes when a single conversion operation is in an infinitely looping state.
- ///
- public class ExceededAllowedIterationsException : Exception
- {
- private readonly string stackTrace;
-
- public ExceededAllowedIterationsException(StackTrace stackTrace)
- {
- this.stackTrace = stackTrace.ToString();
- }
-
- public override string StackTrace => stackTrace;
- public override string ToString() => $"{GetType().Name}: {Message}\r\n{StackTrace}";
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
index 3e9c9feba1..1c9e1e4c73 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
+ Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs
index bfa6bc0a17..03d2ba19cb 100644
--- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
}
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
- public override bool HandleMouseInput => false;
+ public override bool HandlePositionalInput => false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs
index a01947a60b..138a2c0273 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs
@@ -20,8 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
+ Size = Vector2.One
};
-
- protected override Vector2 PlayfieldArea => Vector2.One;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index af2a889f03..6a0457efc6 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -111,6 +111,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
}
+ protected void BeginHold()
+ {
+ holdStartTime = Time.Current;
+ bodyPiece.Hitting = true;
+ }
+
+ protected void EndHold()
+ {
+ holdStartTime = null;
+ bodyPiece.Hitting = false;
+ }
+
public bool OnPressed(ManiaAction action)
{
// Make sure the action happened within the body of the hold note
@@ -123,8 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
// and within the limited range of the above if-statement. This state will be managed by the head note if the
// user has pressed during the hit windows of the head note.
- holdStartTime = Time.Current;
-
+ BeginHold();
return true;
}
@@ -137,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value)
return false;
- holdStartTime = null;
+ EndHold();
// If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit)
@@ -170,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
- holdNote.holdStartTime = Time.Current;
+ holdNote.BeginHold();
return true;
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
index 4ab2da208a..619fe06c73 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
@@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new BufferedContainer
{
+ Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
@@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
private Color4 accentColour;
+
public Color4 AccentColour
{
get { return accentColour; }
@@ -86,6 +88,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
+ public bool Hitting
+ {
+ get { return hitting; }
+ set
+ {
+ hitting = value;
+ updateAccentColour();
+ }
+ }
+
private Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
@@ -118,13 +130,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
+ private bool hitting;
+
private void updateAccentColour()
{
if (!IsLoaded)
return;
- foreground.Colour = AccentColour.Opacity(0.9f);
- background.Colour = AccentColour.Opacity(0.6f);
+ foreground.Colour = AccentColour.Opacity(0.5f);
+ background.Colour = AccentColour.Opacity(0.7f);
+
+ const float animation_length = 50;
+
+ foreground.ClearTransforms(false, nameof(foreground.Colour));
+ if (hitting)
+ {
+ // wait for the next sync point
+ double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
+ using (foreground.BeginDelayedSequence(synchronisedOffset))
+ foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop();
+ }
subtractionCache.Invalidate();
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index d489d48fc3..09976e5994 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
- protected override Container Content => hitObjectArea;
-
public Column()
{
RelativeSizeAxes = Axes.Y;
@@ -54,7 +52,10 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
+ hitObjectArea = new ColumnHitObjectArea(HitObjectContainer)
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
explosionContainer = new Container
{
Name = "Hit explosions",
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index b5dfb0949a..5a4adfd72e 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -8,28 +8,24 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
- public class ColumnHitObjectArea : Container, IHasAccentColour
+ public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
- private Container content;
- protected override Container Content => content;
-
private readonly IBindable direction = new Bindable();
- private Container hitTargetLine;
+ private readonly Container hitTargetLine;
+ private readonly Drawable hitTargetBar;
- [BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
- Drawable hitTargetBar;
-
InternalChildren = new[]
{
hitTargetBar = new Box
@@ -45,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
- content = new Container
- {
- Name = "Hit objects",
- RelativeSizeAxes = Axes.Both,
- },
+ hitObjectContainer
};
+ }
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
index dc66249cd9..f78cfefab8 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
@@ -30,10 +30,11 @@ namespace osu.Game.Rulesets.Mania.UI
if (Result.IsHit)
{
- this.ScaleTo(0.8f);
- this.ScaleTo(1, 250, Easing.OutElastic);
+ JudgementBody.ScaleTo(0.8f);
+ JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
- this.Delay(50).FadeOut(200).ScaleTo(0.75f, 250);
+ JudgementBody.Delay(50).ScaleTo(0.75f, 250);
+ this.Delay(50).FadeOut(200);
}
Expire();
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 6bf63443b5..5c3a618a19 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -25,12 +26,14 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
+ Size = new Vector2(1, 0.8f);
+
GridContainer playfieldGrid;
- InternalChild = playfieldGrid = new GridContainer
+ AddInternal(playfieldGrid = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[] { new Drawable[stageDefinitions.Count] }
- };
+ });
var normalColumnAction = ManiaAction.Key1;
var specialColumnAction = ManiaAction.Special1;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 09ebde2799..49874f6dc1 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -24,7 +24,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -110,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
- protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
-
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
index 4d6c5a747a..8ee0fbf7fe 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
- public class ManiaScrollingPlayfield : ScrollingPlayfield
+ public abstract class ManiaScrollingPlayfield : ScrollingPlayfield
{
private readonly IBindable direction = new Bindable();
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index f292d5ff16..8cf49686b9 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -30,8 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IReadOnlyList Columns => columnFlow.Children;
private readonly FillFlowContainer columnFlow;
- protected override Container Content => barLineContainer;
- private readonly Container barLineContainer;
+ private readonly Container barLineContainer;
public Container Judgements => judgements;
private readonly JudgementContainer judgements;
@@ -105,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
+ Child = HitObjectContainer
}
},
judgements = new JudgementContainer
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
index c2d3aab2ab..6b67188791 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests
}
}
- private class TestDrawableHitCircle : DrawableHitCircle
+ protected class TestDrawableHitCircle : DrawableHitCircle
{
private readonly bool auto;
@@ -94,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
this.auto = auto;
}
+ public void TriggerJudgement() => UpdateResult(true);
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (auto && !userTriggered && timeOffset > 0)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
new file mode 100644
index 0000000000..97978cff1e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestCaseShaking : TestCaseHitCircle
+ {
+ public override void Add(Drawable drawable)
+ {
+ base.Add(drawable);
+
+ if (drawable is TestDrawableHitCircle hitObject)
+ {
+ Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
+ hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
index 3f9464a98f..300ac16155 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(239, 176),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(154, 28),
@@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(distance, 0),
@@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(200, 200),
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(-200, 0),
@@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
CurveType = CurveType.Catmull,
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(50, -50),
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 247d5e18c1..23c6150b6a 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,9 +2,10 @@
-
-
+
+
+
WinExe
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index cfb1b0f050..db80948c94 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
public class OsuBeatmapProcessor : BeatmapProcessor
{
+ private const int stack_distance = 3;
+
public OsuBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
@@ -18,17 +20,21 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
public override void PostProcess()
{
base.PostProcess();
- applyStacking((Beatmap)Beatmap);
+
+ var osuBeatmap = (Beatmap)Beatmap;
+
+ // Reset stacking
+ foreach (var h in osuBeatmap.HitObjects)
+ h.StackHeight = 0;
+
+ if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
+ applyStacking(osuBeatmap);
+ else
+ applyStackingOld(osuBeatmap);
}
private void applyStacking(Beatmap beatmap)
{
- const int stack_distance = 3;
-
- // Reset stacking
- for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++)
- beatmap.HitObjects[i].StackHeight = 0;
-
// Extend the end index to include objects they are stacked on
int extendedEndIndex = beatmap.HitObjects.Count - 1;
for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--)
@@ -167,5 +173,40 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
}
}
}
+
+ private void applyStackingOld(Beatmap beatmap)
+ {
+ for (int i = 0; i < beatmap.HitObjects.Count; i++)
+ {
+ OsuHitObject currHitObject = beatmap.HitObjects[i];
+
+ if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
+ continue;
+
+ double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime;
+ int sliderStack = 0;
+
+ for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
+ {
+ double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
+
+ if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
+ break;
+
+ if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
+ {
+ currHitObject.StackHeight++;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ }
+ else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
+ {
+ //Case for sliders - bump notes down and right, rather than up and left.
+ sliderStack++;
+ beatmap.HitObjects[j].StackHeight -= sliderStack;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ }
+ }
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 5e91ed7a97..8fc2b69267 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sectionLength = section_length * timeRate;
// The first object doesn't generate a strain, so we begin with an incremented section end
- double currentSectionEnd = 2 * sectionLength;
+ double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
{
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
- int maxCombo = beatmap.HitObjects.Count();
+ int maxCombo = beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d4a60dd52f..b0887ac72b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
- beatmapMaxCombo = Beatmap.HitObjects.Count();
+ beatmapMaxCombo = Beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
index 4443a0e66b..24d4677981 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
@@ -23,8 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
- objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
- difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
+ difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
}
///
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 29de23406b..ccfcc1ef25 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -21,15 +21,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
public OsuHitObject BaseObject { get; }
///
- /// Normalized distance from the of the previous .
+ /// Normalized distance from the end position of the previous to the start position of this .
///
- public double Distance { get; private set; }
+ public double JumpDistance { get; private set; }
+
+ ///
+ /// Normalized distance between the start and end position of the previous .
+ ///
+ public double TravelDistance { get; private set; }
///
/// Milliseconds elapsed since the StartTime of the previous .
///
public double DeltaTime { get; private set; }
+ ///
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms.
+ ///
+ public double StrainTime { get; private set; }
+
private readonly OsuHitObject lastObject;
private readonly double timeRate;
@@ -51,31 +61,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private void setDistances()
{
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
- double scalingFactor = normalized_radius / BaseObject.Radius;
+ float scalingFactor = normalized_radius / (float)BaseObject.Radius;
if (BaseObject.Radius < 30)
{
- double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50;
+ float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50;
scalingFactor *= 1 + smallCircleBonus;
}
Vector2 lastCursorPosition = lastObject.StackedPosition;
- float lastTravelDistance = 0;
var lastSlider = lastObject as Slider;
if (lastSlider != null)
{
computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
- lastTravelDistance = lastSlider.LazyTravelDistance;
}
- Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
+ // Don't need to jump to reach spinners
+ if (!(BaseObject is Spinner))
+ JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
+
+ // Todo: BUG!!! Last slider's travel distance is considered ONLY IF we ourselves are also a slider!
+ if (BaseObject is Slider)
+ TravelDistance = (lastSlider?.LazyTravelDistance ?? 0) * scalingFactor;
}
private void setTimingValues()
{
- // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
- DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
+ DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
+
+ // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
+ StrainTime = Math.Max(50, DeltaTime);
}
private void computeSliderCursorPosition(Slider slider)
@@ -87,8 +103,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
float approxFollowCircleRadius = (float)(slider.Radius * 3);
var computeVertex = new Action(t =>
{
+ double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration;
+ if (progress % 2 > 1)
+ progress = 1 - progress % 1;
+ else
+ progress = progress % 1;
+
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
- var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value;
+ var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value;
float dist = diff.Length;
if (dist > approxFollowCircleRadius)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 0a45c62671..f11b6d66f6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
- protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime;
+ protected override double StrainValueOf(OsuDifficultyHitObject current)
+ => (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index b807f20037..1cde03624b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double StrainValueOf(OsuDifficultyHitObject current)
{
- double distance = current.Distance;
+ double distance = current.TravelDistance + current.JumpDistance;
double speedValue;
if (distance > single_spacing_threshold)
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
else
speedValue = 0.95;
- return speedValue / current.DeltaTime;
+ return speedValue / current.StrainTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
index adb28289cf..151564a2a8 100644
--- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
@@ -56,6 +56,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
}
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
- public override bool HandleMouseInput => false;
+ public override bool HandlePositionalInput => false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
index 0f6143a83d..aff42dd233 100644
--- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
body.UpdateProgress(0);
}
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition);
public override Quad SelectionQuad => body.PathDrawQuad;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
index 6efa16bf56..8571de39f4 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.UI;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit
@@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override Vector2 PlayfieldArea => Vector2.One;
-
protected override CursorContainer CreateCursor() => null;
+
+ protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One };
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index dce1fc2851..d6972d55d2 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
@@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit
new HitObjectCompositionTool()
};
- protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both };
+ protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index 7a30e6b134..1b3725a15e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
@@ -33,8 +32,9 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- var newControlPoints = new List();
- slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y)));
+ var newControlPoints = new Vector2[slider.ControlPoints.Length];
+ for (int i = 0; i < slider.ControlPoints.Length; i++)
+ newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y);
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 4047e057cb..8d27502b3c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Input.States;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
const float relax_leniency = 3;
- foreach (var drawable in playfield.HitObjects.AliveObjects)
+ foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
{
if (!(drawable is DrawableOsuHitObject osuHit))
continue;
@@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Mods
wasLeft = !wasLeft;
}
- osuInputManager.HandleCustomInput(new InputState(), state);
+ state.Apply(osuInputManager.CurrentState, osuInputManager);
}
public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
new file mode 100644
index 0000000000..440b314e5f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Transform";
+ public override string ShortenedName => "TR";
+ public override FontAwesome Icon => FontAwesome.fa_arrows;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Everything rotates. EVERYTHING.";
+ public override double ScoreMultiplier => 1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
+
+ private float theta;
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables)
+ {
+ var hitObject = (OsuHitObject) drawable.HitObject;
+
+ float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
+
+ Vector2 originalPosition = drawable.Position;
+ Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
+
+ //the - 1 and + 1 prevents the hit objects to appear in the wrong position.
+ double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
+ double moveDuration = hitObject.TimePreempt + 1;
+
+ using (drawable.BeginAbsoluteSequence(appearTime, true))
+ {
+ drawable
+ .MoveToOffset(appearOffset)
+ .MoveTo(originalPosition, moveDuration, Easing.InOutSine);
+ }
+
+ theta += (float) hitObject.TimeFadeIn / 1000;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
new file mode 100644
index 0000000000..e0a93453ce
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -0,0 +1,72 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Wiggle";
+ public override string ShortenedName => "WG";
+ public override FontAwesome Icon => FontAwesome.fa_certificate;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "They just won't stay still...";
+ public override double ScoreMultiplier => 1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
+
+ private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
+ private const int wiggle_strength = 10; // Higher = stronger wiggles
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables)
+ drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState;
+ }
+
+ private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state)
+ {
+ var osuObject = (OsuHitObject)drawable.HitObject;
+ Vector2 origin = drawable.Position;
+
+ // Wiggle the repeat points with the slider instead of independently.
+ // Also fixes an issue with repeat points being positioned incorrectly.
+ if (osuObject is RepeatPoint)
+ return;
+
+ Random objRand = new Random((int)osuObject.StartTime);
+
+ // Wiggle all objects during TimePreempt
+ int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration;
+
+ void wiggle()
+ {
+ float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
+ float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
+ drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
+ }
+
+ for (int i = 0; i < amountWiggles; i++)
+ using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
+ wiggle();
+
+ // Keep wiggling sliders and spinners for their duration
+ if (!(osuObject is IHasEndTime endTime))
+ return;
+
+ amountWiggles = (int)(endTime.Duration / wiggle_duration);
+
+ for (int i = 0; i < amountWiggles; i++)
+ using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
+ wiggle();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 6344fbb770..4bdddcef11 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -88,7 +88,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
+ {
+ Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
return;
+ }
ApplyResult(r => r.Type = result);
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 0501f8b7a0..10cd246172 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -10,19 +10,29 @@ using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using OpenTK.Graphics;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuHitObject : DrawableHitObject
{
- public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
+ public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt;
+
+ private readonly ShakeContainer shakeContainer;
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
+ base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both });
Alpha = 0;
}
+ // Forward all internal management to shakeContainer.
+ // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
+ protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
+ protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
+ protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable);
+
protected sealed override void UpdateState(ArmedState state)
{
double transformTime = HitObject.StartTime - HitObject.TimePreempt;
@@ -68,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
+ protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
+
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index f48f03f197..89f380db4e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -44,14 +44,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ticks = new Container { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container { RelativeSizeAxes = Axes.Both },
- Ball = new SliderBall(s)
+ Ball = new SliderBall(s, this)
{
BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale),
AlwaysPresent = true,
Alpha = 0
},
- HeadCircle = new DrawableSliderHead(s, s.HeadCircle),
+ HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
+ {
+ OnShake = Shake
+ },
TailCircle = new DrawableSliderTail(s, s.TailCircle)
};
@@ -181,6 +184,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index e823c870f9..6d6cba4936 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
@@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!IsHit)
Position = slider.CurvePositionAt(completionProgress);
}
+
+ public Action OnShake;
+
+ protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index bd7a4ad3f6..6bb6991cc0 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class CirclePiece : Container, IKeyBindingHandler
{
+ // IsHovered is used
+ public override bool HandlePositionalInput => true;
+
public Func Hit;
public CirclePiece()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index b11e4fc971..3081ae49fc 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -5,11 +5,11 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.EventArgs;
-using osu.Framework.Input.States;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Skinning;
+using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -36,9 +36,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private readonly Slider slider;
public readonly Drawable FollowCircle;
private Drawable drawableBall;
+ private readonly DrawableSlider drawableSlider;
- public SliderBall(Slider slider)
+ public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
+ this.drawableSlider = drawableSlider;
this.slider = slider;
Masking = true;
AutoSizeAxes = Axes.Both;
@@ -100,24 +102,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
};
}
- private InputState lastState;
+ private Vector2? lastScreenSpaceMousePosition;
- protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+ protected override bool OnMouseDown(MouseDownEvent e)
{
- lastState = state;
- return base.OnMouseDown(state, args);
+ lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
+ return base.OnMouseDown(e);
}
- protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+ protected override bool OnMouseUp(MouseUpEvent e)
{
- lastState = state;
- return base.OnMouseUp(state, args);
+ lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
+ return base.OnMouseUp(e);
}
- protected override bool OnMouseMove(InputState state)
+ protected override bool OnMouseMove(MouseMoveEvent e)
{
- lastState = state;
- return base.OnMouseMove(state);
+ lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
+ return base.OnMouseMove(e);
}
public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
@@ -151,11 +153,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (Time.Current < slider.EndTime)
{
- // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
+ // Make sure to use the base version of ReceivePositionalInputAt so that we correctly check the position.
Tracking = canCurrentlyTrack
- && lastState != null
- && ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
- && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
+ && lastScreenSpaceMousePosition.HasValue
+ && ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value)
+ && (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 283d6b91f6..f4ccf673e9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -7,26 +7,24 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Lines;
-using osu.Framework.Graphics.Textures;
-using OpenTK;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
+using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBody : Container, ISliderProgress
{
- private readonly Path path;
+ private readonly SliderPath path;
private readonly BufferedContainer container;
public float PathWidth
{
- get { return path.PathWidth; }
- set { path.PathWidth = value; }
+ get => path.PathWidth;
+ set => path.PathWidth = value;
}
///
@@ -42,49 +40,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
- private Color4 accentColour = Color4.White;
///
/// Used to colour the path.
///
public Color4 AccentColour
{
- get { return accentColour; }
+ get => path.AccentColour;
set
{
- if (accentColour == value)
+ if (path.AccentColour == value)
return;
- accentColour = value;
+ path.AccentColour = value;
- if (LoadState >= LoadState.Ready)
- reloadTexture();
+ container.ForceRedraw();
}
}
- private Color4 borderColour = Color4.White;
///
/// Used to colour the path border.
///
public new Color4 BorderColour
{
- get { return borderColour; }
+ get => path.BorderColour;
set
{
- if (borderColour == value)
+ if (path.BorderColour == value)
return;
- borderColour = value;
+ path.BorderColour = value;
- if (LoadState >= LoadState.Ready)
- reloadTexture();
+ container.ForceRedraw();
}
}
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
- private int textureWidth => (int)PathWidth * 2;
-
private Vector2 topLeftOffset;
private readonly Slider slider;
+
public SliderBody(Slider s)
{
slider = s;
@@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
- path = new Path
+ path = new SliderPath
{
Blending = BlendingMode.None,
},
@@ -108,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
container.Attach(RenderbufferInternalFormat.DepthComponent16);
}
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
public void SetRange(double p0, double p1)
{
@@ -130,53 +123,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load()
{
- reloadTexture();
computeSize();
}
- private void reloadTexture()
- {
- var texture = new Texture(textureWidth, 1);
-
- //initialise background
- var raw = new RawTexture(textureWidth, 1);
- var bytes = raw.Data;
-
- const float aa_portion = 0.02f;
- const float border_portion = 0.128f;
- const float gradient_portion = 1 - border_portion;
-
- const float opacity_at_centre = 0.3f;
- const float opacity_at_edge = 0.8f;
-
- for (int i = 0; i < textureWidth; i++)
- {
- float progress = (float)i / (textureWidth - 1);
-
- if (progress <= border_portion)
- {
- bytes[i * 4] = (byte)(BorderColour.R * 255);
- bytes[i * 4 + 1] = (byte)(BorderColour.G * 255);
- bytes[i * 4 + 2] = (byte)(BorderColour.B * 255);
- bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255));
- }
- else
- {
- progress -= border_portion;
-
- bytes[i * 4] = (byte)(AccentColour.R * 255);
- bytes[i * 4 + 1] = (byte)(AccentColour.G * 255);
- bytes[i * 4 + 2] = (byte)(AccentColour.B * 255);
- bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255));
- }
- }
-
- texture.SetData(new TextureUpload(raw));
- path.Texture = texture;
-
- container.ForceRedraw();
- }
-
private void computeSize()
{
// Generate the entire curve
@@ -229,5 +178,53 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
SetRange(start, end);
}
+
+ private class SliderPath : SmoothPath
+ {
+ private const float border_portion = 0.128f;
+ private const float gradient_portion = 1 - border_portion;
+
+ private const float opacity_at_centre = 0.3f;
+ private const float opacity_at_edge = 0.8f;
+
+ private Color4 borderColour = Color4.White;
+
+ public Color4 BorderColour
+ {
+ get => borderColour;
+ set
+ {
+ if (borderColour == value)
+ return;
+ borderColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ private Color4 accentColour = Color4.White;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ protected override Color4 ColourAt(float position)
+ {
+ if (position <= border_portion)
+ return BorderColour;
+
+ position -= border_portion;
+ return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
index 1a7455838f..584fd93a70 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
@@ -11,9 +11,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerBackground : CircularContainer, IHasAccentColour
{
- public override bool HandleKeyboardInput => false;
- public override bool HandleMouseInput => false;
-
protected Box Disc;
public Color4 AccentColour
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 5aba60ba03..4dd1c5f218 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -4,7 +4,7 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.States;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
};
}
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private bool tracking;
public bool Tracking
@@ -68,10 +68,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
- protected override bool OnMouseMove(InputState state)
+ protected override bool OnMouseMove(MouseMoveEvent e)
{
- mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position);
- return base.OnMouseMove(state);
+ mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
+ return base.OnMouseMove(e);
}
private Vector2 mousePosition;
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 7a0dcc77a6..c284335c98 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public SliderCurve Curve { get; } = new SliderCurve();
- public List ControlPoints
+ public Vector2[] ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index e7bbe755a0..e2f4c43a23 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -4,8 +4,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework.Input.Bindings;
-using osu.Framework.Input.EventArgs;
-using osu.Framework.Input.States;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
@@ -36,22 +35,20 @@ namespace osu.Game.Rulesets.Osu
{
}
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => AllowUserPresses && base.OnKeyDown(state, args);
- protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => AllowUserPresses && base.OnKeyUp(state, args);
- protected override bool OnJoystickPress(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickPress(state, args);
- protected override bool OnJoystickRelease(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickRelease(state, args);
- protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => AllowUserPresses && base.OnMouseDown(state, args);
- protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => AllowUserPresses && base.OnMouseUp(state, args);
- protected override bool OnScroll(InputState state) => AllowUserPresses && base.OnScroll(state);
+ protected override bool Handle(UIEvent e)
+ {
+ if (!AllowUserPresses) return false;
+ return base.Handle(e);
+ }
}
}
public enum OsuAction
{
- [Description("Left Button")]
+ [Description("Left button")]
LeftButton,
- [Description("Right Button")]
+ [Description("Right button")]
RightButton
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index fa6e9a018a..6736d10dab 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -117,6 +117,11 @@ namespace osu.Game.Rulesets.Osu
new OsuModRelax(),
new OsuModAutopilot(),
};
+ case ModType.Fun:
+ return new Mod[] {
+ new OsuModTransform(),
+ new OsuModWiggle(),
+ };
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index abcd1ddbda..4b5513ff9c 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -12,7 +12,7 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
-using osu.Framework.Input.States;
+using osu.Framework.Input.Events;
using osu.Framework.Timing;
using OpenTK;
using OpenTK.Graphics;
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, TextureStore textures)
@@ -117,15 +117,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
timeOffset = Time.Current;
}
- protected override bool OnMouseMove(InputState state)
+ protected override bool OnMouseMove(MouseMoveEvent e)
{
- Vector2 pos = state.Mouse.NativeState.Position;
+ Vector2 pos = e.ScreenSpaceMousePosition;
if (lastPosition == null)
{
lastPosition = pos;
resampler.AddPosition(lastPosition.Value);
- return base.OnMouseMove(state);
+ return base.OnMouseMove(e);
}
foreach (Vector2 pos2 in resampler.AddPosition(pos))
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
- return base.OnMouseMove(state);
+ return base.OnMouseMove(e);
}
private void addPosition(Vector2 pos)
@@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Texture.DrawQuad(
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
- DrawInfo.Colour,
+ DrawColourInfo.Colour,
null,
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
index 4d6722b61b..4a45d4fb31 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
return false;
}
- public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input.
+ public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input.
protected override void PopIn()
{
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 703d8764fc..398680cb8d 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -23,29 +23,35 @@ namespace osu.Game.Rulesets.Osu.UI
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
public OsuPlayfield()
- : base(BASE_SIZE.X)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- AddRange(new Drawable[]
+ Size = new Vector2(0.75f);
+
+ InternalChild = new PlayfieldAdjustmentContainer
{
- connectionLayer = new FollowPointRenderer
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Depth = 2,
- },
- judgementLayer = new JudgementContainer
- {
- RelativeSizeAxes = Axes.Both,
- Depth = 1,
- },
- approachCircles = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Depth = -1,
- },
- });
+ connectionLayer = new FollowPointRenderer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = 2,
+ },
+ judgementLayer = new JudgementContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = 1,
+ },
+ HitObjectContainer,
+ approachCircles = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = -1,
+ },
+ }
+ };
}
public override void Add(DrawableHitObject h)
@@ -61,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override void PostProcess()
{
- connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType();
+ connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType();
}
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
@@ -72,7 +78,8 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject)
{
Origin = Anchor.Centre,
- Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
+ Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition,
+ Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale * 1.65f)
};
judgementLayer.Add(explosion);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 4bc6992445..0ea8d3ede8 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -4,7 +4,6 @@
using System.Linq;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
-using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Objects.Drawables;
@@ -58,12 +57,6 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- protected override Vector2 GetAspectAdjustedSize()
- {
- var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y);
- return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y);
- }
-
protected override CursorContainer CreateCursor() => new GameplayCursor();
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs
new file mode 100644
index 0000000000..00d5692fda
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs
@@ -0,0 +1,42 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public class PlayfieldAdjustmentContainer : Container
+ {
+ protected override Container Content => content;
+ private readonly Container content;
+
+ public PlayfieldAdjustmentContainer()
+ {
+ InternalChild = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ FillAspectRatio = 4f / 3,
+ Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
+ };
+ }
+
+ ///
+ /// A which scales its content relative to a target width.
+ ///
+ private class ScalingContainer : Container
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
+ Size = Vector2.Divide(Vector2.One, Scale);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 08a0579561..6ae9a018c5 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,9 +2,10 @@
-
-
+
+
+
WinExe
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index acd6d43284..c2cde332e8 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
{
TaikoHitObject first = x.First();
- if (x.Skip(1).Any())
+ if (x.Skip(1).Any() && !(first is Swell))
first.IsStrong = true;
return first;
}).ToList();
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index f59dc8c1ee..6f7264e23b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool validActionPressed;
+ private bool pressHandledThisFrame;
+
protected DrawableHit(Hit hit)
: base(hit)
{
@@ -51,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(TaikoAction action)
{
+ if (pressHandledThisFrame)
+ return true;
+
if (Judged)
return false;
@@ -62,6 +67,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (IsHit)
HitAction = action;
+ // Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded
+ // E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note
+ pressHandledThisFrame = true;
+
return result;
}
@@ -76,6 +85,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
base.Update();
+ // The input manager processes all input prior to us updating, so this is the perfect time
+ // for us to remove the extra press blocking, before input is handled in the next frame
+ pressHandledThisFrame = false;
+
Size = BaseSize * Parent.RelativeChildSize;
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
index dc683ae2f5..3b430e7ad1 100644
--- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
@@ -17,13 +17,13 @@ namespace osu.Game.Rulesets.Taiko
public enum TaikoAction
{
- [Description("Left (Rim)")]
+ [Description("Left (rim)")]
LeftRim,
- [Description("Left (Centre)")]
+ [Description("Left (centre)")]
LeftCentre,
- [Description("Right (Centre)")]
+ [Description("Right (centre)")]
RightCentre,
- [Description("Right (Rim)")]
+ [Description("Right (rim)")]
RightRim
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
index 4d660918b8..c7eba91564 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
@@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.UI
switch (Result.Type)
{
case HitResult.Good:
- Colour = colours.GreenLight;
+ JudgementBody.Colour = colours.GreenLight;
break;
case HitResult.Great:
- Colour = colours.BlueLight;
+ JudgementBody.Colour = colours.BlueLight;
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs
new file mode 100644
index 0000000000..661a4e135c
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs
@@ -0,0 +1,22 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics.Containers;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public class PlayfieldAdjustmentContainer : Container
+ {
+ private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
+ private const float default_aspect = 16f / 9f;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
+ Size = new Vector2(1, default_relative_height * aspectAdjust);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 325beb38a5..40ed659bd6 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -1,23 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Taiko.Objects;
-using OpenTK;
-using OpenTK.Graphics;
-using osu.Game.Rulesets.Taiko.Judgements;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Extensions.Color4Extensions;
using System.Linq;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Judgements;
+using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Taiko.UI
{
@@ -40,13 +41,12 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override bool UserScrollSpeedAdjustment => false;
+ protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Overlapping;
+
private readonly Container hitExplosionContainer;
private readonly Container kiaiExplosionContainer;
private readonly JudgementContainer judgementContainer;
- protected override Container Content => content;
- private readonly Container content;
-
private readonly Container topLevelHitContainer;
private readonly Container barlineContainer;
@@ -61,140 +61,147 @@ namespace osu.Game.Rulesets.Taiko.UI
{
Direction.Value = ScrollingDirection.Left;
- AddRangeInternal(new Drawable[]
+ InternalChild = new PlayfieldAdjustmentContainer
{
- backgroundContainer = new Container
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Name = "Transparent playfield background",
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- EdgeEffect = new EdgeEffectParameters
+ backgroundContainer = new Container
{
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(0.2f),
- Radius = 5,
- },
- Children = new Drawable[]
- {
- background = new Box
+ Name = "Transparent playfield background",
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
{
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.6f
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(0.2f),
+ Radius = 5,
},
- }
- },
- new Container
- {
- Name = "Right area",
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = left_area_size },
- Children = new Drawable[]
- {
- new Container
+ Children = new Drawable[]
{
- Name = "Masked elements before hit objects",
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Masking = true,
- Children = new Drawable[]
+ background = new Box
{
- hitExplosionContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Blending = BlendingMode.Additive,
- },
- new HitTarget
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit
- }
- }
- },
- barlineContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }
- },
- content = new Container
- {
- Name = "Hit objects",
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Masking = true
- },
- kiaiExplosionContainer = new Container
- {
- Name = "Kiai hit explosions",
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Blending = BlendingMode.Additive
- },
- judgementContainer = new JudgementContainer
- {
- Name = "Judgements",
- RelativeSizeAxes = Axes.Y,
- Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Blending = BlendingMode.Additive
- },
- }
- },
- overlayBackgroundContainer = new Container
- {
- Name = "Left overlay",
- RelativeSizeAxes = Axes.Y,
- Size = new Vector2(left_area_size, 1),
- Children = new Drawable[]
- {
- overlayBackground = new Box
- {
- RelativeSizeAxes = Axes.Both,
- },
- new InputDrum(controlPoints)
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Scale = new Vector2(0.9f),
- Margin = new MarginPadding { Right = 20 }
- },
- new Box
- {
- Anchor = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- Width = 10,
- Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
- },
- }
- },
- new Container
- {
- Name = "Border",
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- MaskingSmoothness = 0,
- BorderThickness = 2,
- AlwaysPresent = true,
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.6f
+ },
}
+ },
+ new Container
+ {
+ Name = "Right area",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = left_area_size },
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Name = "Masked elements before hit objects",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
+ Masking = true,
+ Children = new Drawable[]
+ {
+ hitExplosionContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Blending = BlendingMode.Additive,
+ },
+ new HitTarget
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit
+ }
+ }
+ },
+ barlineContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }
+ },
+ new Container
+ {
+ Name = "Hit objects",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
+ Masking = true,
+ Child = HitObjectContainer
+ },
+ kiaiExplosionContainer = new Container
+ {
+ Name = "Kiai hit explosions",
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
+ Blending = BlendingMode.Additive
+ },
+ judgementContainer = new JudgementContainer
+ {
+ Name = "Judgements",
+ RelativeSizeAxes = Axes.Y,
+ Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
+ Blending = BlendingMode.Additive
+ },
+ }
+ },
+ overlayBackgroundContainer = new Container
+ {
+ Name = "Left overlay",
+ RelativeSizeAxes = Axes.Y,
+ Size = new Vector2(left_area_size, 1),
+ Children = new Drawable[]
+ {
+ overlayBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new InputDrum(controlPoints)
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Scale = new Vector2(0.9f),
+ Margin = new MarginPadding { Right = 20 }
+ },
+ new Box
+ {
+ Anchor = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Y,
+ Width = 10,
+ Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
+ },
+ }
+ },
+ new Container
+ {
+ Name = "Border",
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ MaskingSmoothness = 0,
+ BorderThickness = 2,
+ AlwaysPresent = true,
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ },
+ topLevelHitContainer = new Container
+ {
+ Name = "Top level hit objects",
+ RelativeSizeAxes = Axes.Both,
}
- },
- topLevelHitContainer = new Container
- {
- Name = "Top level hit objects",
- RelativeSizeAxes = Axes.Both,
}
- });
+ };
- VisibleTimeRange.Value = 6000;
+ VisibleTimeRange.Value = 7000;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 229ab69ceb..3d08bffe0f 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
-using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
@@ -13,7 +12,6 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
-using OpenTK;
using System.Linq;
using osu.Framework.Input;
using osu.Game.Input.Handlers;
@@ -74,27 +72,11 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
- protected override Vector2 GetAspectAdjustedSize()
- {
- const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
- const float default_aspect = 16f / 9f;
-
- float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect;
-
- return new Vector2(1, default_relative_height * aspectAdjust);
- }
-
- protected override Vector2 PlayfieldArea => Vector2.One;
-
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
- protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft
- };
+ protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h)
{
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index d3351f86f8..af63a39662 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -165,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
[Test]
- public void TestDecodeBeatmapColors()
+ public void TestDecodeBeatmapColours()
{
var decoder = new LegacySkinDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
@@ -181,6 +181,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
+ new Color4(100, 100, 100, 100),
};
Assert.AreEqual(expectedColors.Length, comboColors.Count);
for (int i = 0; i < expectedColors.Length; i++)
diff --git a/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu b/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu
index 3e44dc0af8..67570ad21b 100644
--- a/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu
+++ b/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu
@@ -101,6 +101,7 @@ Combo3 : 128,255,255
Combo4 : 128,255,128
Combo5 : 255,187,255
Combo6 : 255,177,140
+Combo7 : 100,100,100,100
[HitObjects]
192,168,956,6,0,P|184:128|200:80,1,90,4|0,1:2|0:0,0:0:0:0:
diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs
index 361e255894..1f409f043e 100644
--- a/osu.Game.Tests/Visual/TestCaseCursors.cs
+++ b/osu.Game.Tests/Visual/TestCaseCursors.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.States;
+using osu.Framework.Input.Events;
using osu.Framework.MathUtils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
@@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual
///
/// The cursor to check.
private bool checkAtMouse(CursorContainer cursorContainer)
- => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition));
+ => Precision.AlmostEquals(InputManager.CurrentState.Mouse.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition));
private class CustomCursorBox : Container, IProvideCursor
{
@@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual
public CursorContainer Cursor { get; }
public bool ProvidingUserCursor { get; }
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor;
private readonly Box background;
@@ -224,16 +224,16 @@ namespace osu.Game.Tests.Visual
};
}
- protected override bool OnHover(InputState state)
+ protected override bool OnHover(HoverEvent e)
{
background.FadeTo(0.4f, 250, Easing.OutQuint);
return false;
}
- protected override void OnHoverLost(InputState state)
+ protected override void OnHoverLost(HoverLostEvent e)
{
background.FadeTo(0.1f, 250);
- base.OnHoverLost(state);
+ base.OnHoverLost(e);
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
index 45b5ec2c11..417b0f94d7 100644
--- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
@@ -8,14 +8,14 @@ using System.Linq;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.EventArgs;
using osu.Framework.Logging;
using osu.Game.Screens.Play;
+using OpenTK;
namespace osu.Game.Tests.Visual
{
[Description("player pause/fail screens")]
- public class TestCaseGameplayMenuOverlay : OsuTestCase
+ public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
@@ -73,12 +73,18 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => failOverlay.Show());
- AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null));
+ AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First()));
AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected));
}
+ private void press(Key key)
+ {
+ InputManager.PressKey(key);
+ InputManager.ReleaseKey(key);
+ }
+
///
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
///
@@ -86,7 +92,7 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => pauseOverlay.Show());
- AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }));
+ AddStep("Press enter", () => press(Key.Enter));
AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
AddStep("Hide overlay", () => pauseOverlay.Hide());
@@ -99,7 +105,7 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => pauseOverlay.Show());
- AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
+ AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
@@ -112,7 +118,7 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => pauseOverlay.Show());
- AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
+ AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
@@ -125,11 +131,11 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => failOverlay.Show());
- AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
+ AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
- AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
+ AddStep("Up arrow", () => press(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
- AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
+ AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
@@ -142,11 +148,11 @@ namespace osu.Game.Tests.Visual
{
AddStep("Show overlay", () => failOverlay.Show());
- AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
+ AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
- AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
+ AddStep("Down arrow", () => press(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
- AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
+ AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
@@ -161,8 +167,8 @@ 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.TriggerOnMouseMove(null));
+ AddStep("Down arrow", () => press(Key.Down));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected);
AddAssert("Second button selected", () => secondButton.Selected);
@@ -174,12 +180,16 @@ namespace osu.Game.Tests.Visual
///
private void testKeySelectionAfterMouseSelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ AddStep("Show overlay", () =>
+ {
+ pauseOverlay.Show();
+ InputManager.MoveMouseTo(Vector2.Zero);
+ });
var secondButton = pauseOverlay.Buttons.Skip(1).First();
- AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null));
- AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Up arrow", () => press(Key.Up));
AddAssert("Second button not selected", () => !secondButton.Selected);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
@@ -195,9 +205,9 @@ namespace osu.Game.Tests.Visual
var secondButton = pauseOverlay.Buttons.Skip(1).First();
- 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 }));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
+ AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
@@ -218,7 +228,7 @@ namespace osu.Game.Tests.Visual
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
- retryButton.TriggerOnClick();
+ retryButton.Click();
pauseOverlay.OnRetry = lastAction;
});
@@ -235,23 +245,28 @@ namespace osu.Game.Tests.Visual
AddStep("Select second button", () =>
{
- pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
- pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
+ press(Key.Down);
+ press(Key.Down);
});
- var retryButton = pauseOverlay.Buttons.Skip(1).First();
-
bool triggered = false;
+ Action lastAction = null;
AddStep("Press enter", () =>
{
- var lastAction = pauseOverlay.OnRetry;
+ lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
-
- retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter });
- pauseOverlay.OnRetry = lastAction;
+ press(Key.Enter);
});
- AddAssert("Action was triggered", () => triggered);
+ AddAssert("Action was triggered", () =>
+ {
+ if (lastAction != null)
+ {
+ pauseOverlay.OnRetry = lastAction;
+ lastAction = null;
+ }
+ return triggered;
+ });
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
index 5df371dd09..a2bc28bc3c 100644
--- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual
new Slider
{
Position = new Vector2(128, 256),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(216, 0),
diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
index b98875cd6a..178b47ed33 100644
--- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
+++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
@@ -1,32 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// 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.MathUtils;
+using osu.Framework.Timing;
using osu.Game.Screens.Play;
using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseKeyCounter : OsuTestCase
+ public class TestCaseKeyCounter : ManualInputManagerTestCase
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(KeyCounterKeyboard),
+ typeof(KeyCounterMouse),
+ typeof(KeyCounterCollection)
+ };
+
public TestCaseKeyCounter()
{
+ KeyCounterKeyboard rewindTestKeyCounterKeyboard;
KeyCounterCollection kc = new KeyCounterCollection
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
- new KeyCounterKeyboard(Key.Z),
+ rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
},
};
+
AddStep("Add random", () =>
{
Key key = (Key)((int)Key.A + RNG.Next(26));
@@ -34,7 +47,57 @@ namespace osu.Game.Tests.Visual
});
AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
+ Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
+ double time1 = 0;
+
+ AddStep($"Press {testKey} key", () =>
+ {
+ InputManager.PressKey(testKey);
+ InputManager.ReleaseKey(testKey);
+ });
+
+ AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
+
+ AddStep($"Press {testKey} key", () =>
+ {
+ InputManager.PressKey(testKey);
+ InputManager.ReleaseKey(testKey);
+ time1 = Clock.CurrentTime;
+ });
+
+ AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
+
+ IFrameBasedClock oldClock = null;
+
+ AddStep($"Rewind {testKey} counter once", () =>
+ {
+ oldClock = rewindTestKeyCounterKeyboard.Clock;
+ rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
+ });
+
+ AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
+
+ AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
+
+ AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
+
+ AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
+
Add(kc);
}
+
+ private class FixedClock : IClock
+ {
+ private readonly double time;
+
+ public FixedClock(double time)
+ {
+ this.time = time;
+ }
+
+ public double CurrentTime => time;
+ public double Rate => 1;
+ public bool IsRunning => false;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
index e4cb848d90..041fce6ce3 100644
--- a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load()
{
- AddInternal(trackManager);
+ Add(trackManager);
}
[Test]
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
TestTrackOwner owner = null;
PreviewTrack track = null;
- AddStep("get track", () => AddInternal(owner = new TestTrackOwner(track = getTrack())));
+ AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddStep("start", () => track.Start());
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
AddAssert("not stopped", () => track.IsRunning);
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual
{
var track = getTrack();
- AddInternal(track);
+ Add(track);
return track;
}
diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
index bbc9d2b860..b254325472 100644
--- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
@@ -121,14 +121,21 @@ namespace osu.Game.Tests.Visual
Direction = direction;
Padding = new MarginPadding(2);
- Content.Masking = true;
- AddInternal(new Box
+ InternalChildren = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.5f,
- Depth = float.MaxValue
- });
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.5f,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = HitObjectContainer
+ }
+ };
}
}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index d638af0c38..520e0b8940 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -2,9 +2,11 @@
-
-
+
+
+
+
WinExe
diff --git a/osu.Game.props b/osu.Game.props
index ec859e64a5..4bcac479a0 100644
--- a/osu.Game.props
+++ b/osu.Game.props
@@ -1,7 +1,7 @@
- 7
+ 7.2
..\app.manifest
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 9aabb434a3..4ef7b28d49 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Beatmaps
[JsonConverter(typeof(TypedListConverter))]
public List HitObjects = new List();
- IEnumerable IBeatmap.HitObjects => HitObjects;
+ IReadOnlyList IBeatmap.HitObjects => HitObjects;
public virtual IEnumerable GetStatistics() => Enumerable.Empty();
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index a1bb70135a..3cb7833a12 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -55,39 +55,40 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo;
- beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
+ beatmap.HitObjects = convertHitObjects(original.HitObjects, original);
beatmap.Breaks = original.Breaks;
return beatmap;
}
- ///
- /// Converts a hit object.
- ///
- /// The hit object to convert.
- /// The un-converted Beatmap.
- /// The converted hit object.
- private IEnumerable convert(HitObject original, IBeatmap beatmap)
+ private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap)
{
- // Check if the hitobject is already the converted type
- T tObject = original as T;
- if (tObject != null)
- {
- yield return tObject;
- yield break;
- }
+ var result = new List(hitObjects.Count);
- var converted = ConvertHitObject(original, beatmap).ToList();
- ObjectConverted?.Invoke(original, converted);
-
- // Convert the hit object
- foreach (var obj in converted)
+ foreach (var obj in hitObjects)
{
- if (obj == null)
+ if (obj is T tObj)
+ {
+ result.Add(tObj);
continue;
+ }
- yield return obj;
+ var converted = ConvertHitObject(obj, beatmap);
+
+ if (ObjectConverted != null)
+ {
+ converted = converted.ToList();
+ ObjectConverted.Invoke(obj, converted);
+ }
+
+ foreach (var c in converted)
+ {
+ if (c != null)
+ result.Add(c);
+ }
}
+
+ return result;
}
///
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 303a19aab3..1aa4818393 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -27,13 +27,15 @@ namespace osu.Game.Beatmaps
[JsonProperty("id")]
public int? OnlineBeatmapID
{
- get { return onlineBeatmapID; }
- set { onlineBeatmapID = value > 0 ? value : null; }
+ get => onlineBeatmapID;
+ set => onlineBeatmapID = value > 0 ? value : null;
}
[JsonIgnore]
public int BeatmapSetInfoID { get; set; }
+ public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
+
[Required]
public BeatmapSetInfo BeatmapSet { get; set; }
@@ -82,7 +84,7 @@ namespace osu.Game.Beatmaps
[JsonIgnore]
public string StoredBookmarks
{
- get { return string.Join(",", Bookmarks); }
+ get => string.Join(",", Bookmarks);
set
{
if (string.IsNullOrEmpty(value))
@@ -93,8 +95,7 @@ namespace osu.Game.Beatmaps
Bookmarks = value.Split(',').Select(v =>
{
- int val;
- bool result = int.TryParse(v, out val);
+ bool result = int.TryParse(v, out int val);
return new { result, val };
}).Where(p => p.result).Select(p => p.val).ToArray();
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 1c28b533d2..aa653d88f9 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
@@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -62,6 +63,8 @@ namespace osu.Game.Beatmaps
public override string[] HandledExtensions => new[] { ".osz" };
+ protected override string ImportFromStablePath => "Songs";
+
private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps;
@@ -72,11 +75,6 @@ namespace osu.Game.Beatmaps
private readonly List currentDownloads = new List();
- ///
- /// Set a storage with access to an osu-stable install for import purposes.
- ///
- public Func GetStableStorage { private get; set; }
-
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
{
@@ -103,6 +101,11 @@ namespace osu.Game.Beatmaps
b.BeatmapSet = beatmapSet;
}
+ validateOnlineIds(beatmapSet.Beatmaps);
+
+ foreach (BeatmapInfo b in beatmapSet.Beatmaps)
+ fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps);
+
// check if a set already exists with the same online id, delete if it does.
if (beatmapSet.OnlineBeatmapSetID != null)
{
@@ -114,11 +117,6 @@ namespace osu.Game.Beatmaps
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
}
}
-
- validateOnlineIds(beatmapSet.Beatmaps);
-
- foreach (BeatmapInfo b in beatmapSet.Beatmaps)
- fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps);
}
private void validateOnlineIds(List beatmaps)
@@ -195,7 +193,7 @@ namespace osu.Game.Beatmaps
downloadNotification.CompletionClickAction = () =>
{
- PresentBeatmap?.Invoke(importedBeatmap);
+ PresentCompletedImport(importedBeatmap.Yield());
return true;
};
downloadNotification.State = ProgressNotificationState.Completed;
@@ -231,6 +229,12 @@ namespace osu.Game.Beatmaps
BeatmapDownloadBegan?.Invoke(request);
}
+ protected override void PresentCompletedImport(IEnumerable imported)
+ {
+ base.PresentCompletedImport(imported);
+ PresentBeatmap?.Invoke(imported.LastOrDefault());
+ }
+
///
/// Get an existing download request if it exists.
///
@@ -311,27 +315,6 @@ namespace osu.Game.Beatmaps
/// Results from the provided query.
public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
- ///
- /// Denotes whether an osu-stable installation is present to perform automated imports from.
- ///
- public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
-
- ///
- /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
- ///
- public async Task ImportFromStable()
- {
- var stable = GetStableStorage?.Invoke();
-
- if (stable == null)
- {
- Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
- return;
- }
-
- await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs").Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
- }
-
///
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
///
@@ -350,7 +333,11 @@ namespace osu.Game.Beatmaps
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
- if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
+ if (string.IsNullOrEmpty(mapName))
+ {
+ Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database);
+ return null;
+ }
Beatmap beatmap;
using (var stream = new StreamReader(reader.GetStream(mapName)))
@@ -401,21 +388,22 @@ namespace osu.Game.Beatmaps
}
///
- /// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
+ /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status.
///
/// The beatmap to populate.
/// The other beatmaps contained within this set.
/// Whether to re-query if the provided beatmap already has populated values.
/// True if population was successful.
- private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false)
+ private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false)
{
if (api?.State != APIState.Online)
return false;
- if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
+ if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null
+ && beatmap.Status != BeatmapSetOnlineStatus.None && beatmap.BeatmapSet.Status != BeatmapSetOnlineStatus.None)
return true;
- Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
+ Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database);
try
{
@@ -427,6 +415,9 @@ namespace osu.Game.Beatmaps
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
+ beatmap.Status = res.Status;
+ beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
+
if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID))
{
Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database);
@@ -435,6 +426,7 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
+
return true;
}
catch (Exception e)
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 43ae30f780..77ff53b893 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats;
-using osu.Game.Graphics.Textures;
using osu.Game.Skinning;
using osu.Game.Storyboards;
@@ -45,6 +44,10 @@ namespace osu.Game.Beatmaps
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
+ private LargeTextureStore textureStore;
+
+ protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
+
protected override Texture GetBackground()
{
if (Metadata?.BackgroundFile == null)
@@ -52,7 +55,7 @@ namespace osu.Game.Beatmaps
try
{
- return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile));
+ return (textureStore ?? (textureStore = new LargeTextureStore(new TextureLoaderStore(store)))).Get(getPathForFile(Metadata.BackgroundFile));
}
catch
{
@@ -73,6 +76,14 @@ namespace osu.Game.Beatmaps
}
}
+ public override void TransferTo(WorkingBeatmap other)
+ {
+ base.TransferTo(other);
+
+ if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
+ owb.textureStore = textureStore;
+ }
+
protected override Waveform GetWaveform()
{
try
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index 9d7cd673dc..9db2c5f08e 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
@@ -44,6 +45,25 @@ namespace osu.Game.Beatmaps
public virtual void PostProcess()
{
+ void updateNestedCombo(HitObject obj, int comboIndex, int indexInCurrentCombo)
+ {
+ if (obj is IHasComboInformation objectComboInfo)
+ {
+ objectComboInfo.ComboIndex = comboIndex;
+ objectComboInfo.IndexInCurrentCombo = indexInCurrentCombo;
+ foreach (var nestedObject in obj.NestedHitObjects)
+ updateNestedCombo(nestedObject, comboIndex, indexInCurrentCombo);
+ }
+ }
+
+ foreach (var hitObject in Beatmap.HitObjects)
+ {
+ if (hitObject is IHasComboInformation objectComboInfo)
+ {
+ foreach (var nested in hitObject.NestedHitObjects)
+ updateNestedCombo(nested, objectComboInfo.ComboIndex, objectComboInfo.IndexInCurrentCombo);
+ }
+ }
}
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index ebebe42097..7a7d010a31 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -17,10 +17,12 @@ namespace osu.Game.Beatmaps
public int? OnlineBeatmapSetID
{
- get { return onlineBeatmapSetID; }
- set { onlineBeatmapSetID = value > 0 ? value : null; }
+ get => onlineBeatmapSetID;
+ set => onlineBeatmapSetID = value > 0 ? value : null;
}
+ public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
+
public BeatmapMetadata Metadata { get; set; }
public List Beatmaps { get; set; }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index dad69321a5..f064d53358 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -55,14 +55,14 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time to find the sound control point at.
/// The sound control point.
- public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault());
+ public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
///
/// Finds the timing control point that is active at .
///
/// The time to find the timing control point at.
/// The timing control point.
- public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
+ public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
///
/// Finds the maximum BPM represented by any timing control point.
@@ -104,17 +104,26 @@ namespace osu.Game.Beatmaps.ControlPoints
if (time < list[0].Time)
return prePoint ?? new T();
- int index = list.BinarySearch(new T { Time = time });
+ if (time >= list[list.Count - 1].Time)
+ return list[list.Count - 1];
- // Check if we've found an exact match (t == time)
- if (index >= 0)
- return list[index];
+ int l = 0;
+ int r = list.Count - 2;
- index = ~index;
+ while (l <= r)
+ {
+ int pivot = l + ((r - l) >> 1);
- // BinarySearch will return the index of the first element _greater_ than the search
- // This is the inactive point - the active point is the one before it (index - 1)
- return list[index - 1];
+ if (list[pivot].Time < time)
+ l = pivot + 1;
+ else if (list[pivot].Time > time)
+ r = pivot - 1;
+ else
+ return list[pivot];
+ }
+
+ // l will be the first control point with Time > time, but we want the one before it
+ return list[l - 1];
}
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index eb60133fed..81eddaa43a 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The beat length at this control point.
///
- public double BeatLength
+ public virtual double BeatLength
{
get => beatLength;
set => beatLength = MathHelper.Clamp(value, 6, 60000);
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
index c7e97cef55..da281b4db3 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -14,20 +13,35 @@ namespace osu.Game.Beatmaps.Drawables
{
private readonly OsuSpriteText statusText;
- private BeatmapSetOnlineStatus status = BeatmapSetOnlineStatus.None;
+ private BeatmapSetOnlineStatus status;
+
public BeatmapSetOnlineStatus Status
{
- get { return status; }
+ get => status;
set
{
- if (value == status) return;
+ if (status == value)
+ return;
status = value;
- statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpperInvariant();
+ Alpha = value == BeatmapSetOnlineStatus.None ? 0 : 1;
+ statusText.Text = value.ToString().ToUpperInvariant();
}
}
- public BeatmapSetOnlineStatusPill(float textSize, MarginPadding textPadding)
+ public float TextSize
+ {
+ get => statusText.TextSize;
+ set => statusText.TextSize = value;
+ }
+
+ public MarginPadding TextPadding
+ {
+ get => statusText.Padding;
+ set => statusText.Padding = value;
+ }
+
+ public BeatmapSetOnlineStatusPill()
{
AutoSizeAxes = Axes.Both;
Masking = true;
@@ -45,10 +59,10 @@ namespace osu.Game.Beatmaps.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = @"Exo2.0-Bold",
- TextSize = textSize,
- Padding = textPadding,
},
};
+
+ Status = BeatmapSetOnlineStatus.None;
}
}
}
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
index 2927654f62..6f45718390 100644
--- a/osu.Game/Beatmaps/Formats/Decoder.cs
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -55,11 +55,11 @@ namespace osu.Game.Beatmaps.Formats
} while (line != null && line.Length == 0);
if (line == null)
- throw new IOException(@"Unknown file format");
+ throw new IOException(@"Unknown file format (null)");
- var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault();
+ var decoder = typedDecoders.Select(d => line.StartsWith(d.Key, StringComparison.InvariantCulture) ? d.Value : null).FirstOrDefault();
if (decoder == null)
- throw new IOException(@"Unknown file format");
+ throw new IOException($@"Unknown file format ({line})");
return (Decoder)decoder.Invoke(line);
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 31a7698f50..71b7a65ccb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -5,6 +5,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
+using osu.Framework.IO.File;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
@@ -58,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
}
- protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_");
+ protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal);
protected override void ParseLine(Beatmap beatmap, Section section, string line)
{
@@ -100,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats
switch (pair.Key)
{
case @"AudioFilename":
- metadata.AudioFile = pair.Value;
+ metadata.AudioFile = FileSafety.PathStandardise(pair.Value);
break;
case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
@@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
{
case EventType.Background:
string filename = split[2].Trim('"');
- beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename);
break;
case EventType.Break:
var breakEvent = new BreakPeriod
@@ -318,12 +319,12 @@ namespace osu.Game.Beatmaps.Formats
if (timingChange)
{
- handleTimingControlPoint(new TimingControlPoint
- {
- Time = time,
- BeatLength = beatLength,
- TimeSignature = timeSignature
- });
+ var controlPoint = CreateTimingControlPoint();
+ controlPoint.Time = time;
+ controlPoint.BeatLength = beatLength;
+ controlPoint.TimeSignature = timeSignature;
+
+ handleTimingControlPoint(controlPoint);
}
handleDifficultyControlPoint(new DifficultyControlPoint
@@ -408,11 +409,8 @@ namespace osu.Game.Beatmaps.Formats
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
var obj = parser.Parse(line);
-
if (obj != null)
- {
beatmap.HitObjects.Add(obj);
- }
}
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
@@ -421,6 +419,8 @@ namespace osu.Game.Beatmaps.Formats
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
+ protected virtual TimingControlPoint CreateTimingControlPoint() => new TimingControlPoint();
+
[Flags]
internal enum EffectFlags
{
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e9f37e583b..2ef7c5de30 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Formats
protected string StripComments(string line)
{
- var index = line.IndexOf("//", StringComparison.Ordinal);
+ var index = line.AsSpan().IndexOf("//".AsSpan());
if (index > 0)
return line.Substring(0, index);
return line;
@@ -85,13 +85,19 @@ namespace osu.Game.Beatmaps.Formats
string[] split = pair.Value.Split(',');
- if (split.Length != 3)
- throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
+ if (split.Length != 3 && split.Length != 4)
+ throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
- if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b))
+ Color4 colour;
+
+ try
+ {
+ colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255);
+ }
+ catch (Exception e)
+ {
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
-
- Color4 colour = new Color4(r, g, b, 255);
+ }
if (isCombo)
{
diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
new file mode 100644
index 0000000000..13a71aac3d
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Game.Beatmaps.ControlPoints;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ ///
+ /// A built for difficulty calculation of legacy s
+ ///
+ /// To use this, the decoder must be registered by the application through .
+ /// Doing so will override any existing decoders.
+ ///
+ ///
+ public class LegacyDifficultyCalculatorBeatmapDecoder : LegacyBeatmapDecoder
+ {
+ public LegacyDifficultyCalculatorBeatmapDecoder(int version = LATEST_VERSION)
+ : base(version)
+ {
+ ApplyOffsets = false;
+ }
+
+ public new static void Register()
+ {
+ AddDecoder(@"osu file format v", m => new LegacyDifficultyCalculatorBeatmapDecoder(int.Parse(m.Split('v').Last())));
+ }
+
+ protected override TimingControlPoint CreateTimingControlPoint()
+ => new LegacyDifficultyCalculatorControlPoint();
+
+ private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint
+ {
+ public override double BeatLength { get; set; } = 1000;
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 1063dfc923..375c0b29c0 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -269,9 +269,9 @@ namespace osu.Game.Beatmaps.Formats
return Anchor.BottomCentre;
case LegacyOrigins.BottomRight:
return Anchor.BottomRight;
+ default:
+ return Anchor.TopLeft;
}
-
- throw new InvalidDataException($@"Unknown origin: {value}");
}
private void handleVariables(string line)
@@ -298,6 +298,6 @@ namespace osu.Game.Beatmaps.Formats
}
}
- private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"')));
+ private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('"'));
}
}
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index fe20bce98a..3d8b94dc46 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps
///
/// The hitobjects contained by this beatmap.
///
- IEnumerable HitObjects { get; }
+ IReadOnlyList HitObjects { get; }
///
/// Returns statistics for the contained in this beatmap.
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 6c906bb1e4..e0a22460ef 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -8,10 +8,10 @@ using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using osu.Game.Storyboards;
using osu.Framework.IO.File;
using System.IO;
+using System.Threading;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
@@ -38,12 +38,26 @@ namespace osu.Game.Beatmaps
Mods.ValueChanged += mods => applyRateAdjustments();
- beatmap = new AsyncLazy(populateBeatmap);
- background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed);
- track = new AsyncLazy