diff --git a/.vscode/launch.json b/.vscode/launch.json
index ed67fa92cc..e9b8d6f397 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -24,7 +28,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -33,11 +41,15 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -46,12 +58,16 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
}
]
-}
\ No newline at end of file
+}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 29d3b0e394..0289db20f0 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 14d913cffa..4b95a6754e 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
@@ -12,11 +13,14 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
+ [TestFixture]
public class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
- [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
+ [TestCase("basic")]
+ [TestCase("spinner")]
+ [TestCase("spinner-and-circles")]
public new void Test(string name)
{
base.Test(name);
@@ -27,22 +31,15 @@ namespace osu.Game.Rulesets.Catch.Tests
if (hitObject is JuiceStream stream)
{
foreach (var nested in stream.NestedHitObjects)
- {
- yield return new ConvertValue
- {
- StartTime = nested.StartTime,
- Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
- };
- }
+ yield return new ConvertValue((CatchHitObject)nested);
+ }
+ else if (hitObject is BananaShower shower)
+ {
+ foreach (var nested in shower.NestedHitObjects)
+ yield return new ConvertValue((CatchHitObject)nested);
}
else
- {
- yield return new ConvertValue
- {
- StartTime = hitObject.StartTime,
- Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
- };
- }
+ yield return new ConvertValue((CatchHitObject)hitObject);
}
protected override Ruleset CreateRuleset() => new CatchRuleset();
@@ -55,8 +52,31 @@ namespace osu.Game.Rulesets.Catch.Tests
///
private const float conversion_lenience = 2;
- public double StartTime;
- public float Position;
+ [JsonIgnore]
+ public readonly CatchHitObject HitObject;
+
+ public ConvertValue(CatchHitObject hitObject)
+ {
+ HitObject = hitObject;
+ startTime = 0;
+ position = 0;
+ }
+
+ private double startTime;
+
+ public double StartTime
+ {
+ get => HitObject?.StartTime ?? startTime;
+ set => startTime = value;
+ }
+
+ private float position;
+
+ public float Position
+ {
+ get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
+ set => position = value;
+ }
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
index 5119260c53..0ba6398ced 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
+ public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperdashState(status ? 2 : 1);
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
index e77dd76353..5c41e4136c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
Fruit fruit = index == 5
- ? new BananaShower.Banana
+ ? new Banana
{
StartTime = 1000000000000,
IndexInBeatmap = index,
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index ad500606ed..68a8dfb7d3 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -26,22 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo;
var endTime = obj as IHasEndTime;
-
- if (positionData == null)
- {
- if (endTime != null)
- {
- yield return new BananaShower
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- Duration = endTime.Duration,
- NewCombo = comboData?.NewCombo ?? false
- };
- }
-
- yield break;
- }
+ var legacyOffset = obj as IHasLegacyLastTickOffset;
if (curveData != null)
{
@@ -54,20 +39,31 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
- X = positionData.X / CatchPlayfield.BASE_WIDTH,
+ X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
+ NewCombo = comboData?.NewCombo ?? false,
+ LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
+ };
+ }
+ else if (endTime != null)
+ {
+ yield return new BananaShower
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
-
- yield break;
}
-
- yield return new Fruit
+ else
{
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- NewCombo = comboData?.NewCombo ?? false,
- X = positionData.X / CatchPlayfield.BASE_WIDTH
- };
+ yield return new Fruit
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ NewCombo = comboData?.NewCombo ?? false,
+ X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
+ };
+ }
}
protected override Beatmap CreateBeatmap() => new CatchBeatmap();
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index e16f5fcb60..ab0afb08d7 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -9,11 +9,14 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
+using osu.Game.Rulesets.Catch.MathUtils;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapProcessor : BeatmapProcessor
{
+ public const int RNG_SEED = 1337;
+
public CatchBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
@@ -21,13 +24,52 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override void PostProcess()
{
- initialiseHyperDash((List)Beatmap.HitObjects);
-
base.PostProcess();
+ applyPositionOffsets();
+
+ initialiseHyperDash((List)Beatmap.HitObjects);
+
int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType())
+ {
obj.IndexInBeatmap = index++;
+ if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
+ lastNested.LastInCombo = true;
+ }
+ }
+
+ private void applyPositionOffsets()
+ {
+ var rng = new FastRandom(RNG_SEED);
+ // todo: HardRock displacement should be applied here
+
+ foreach (var obj in Beatmap.HitObjects)
+ {
+ switch (obj)
+ {
+ case BananaShower bananaShower:
+ foreach (var banana in bananaShower.NestedHitObjects.OfType())
+ {
+ banana.X = (float)rng.NextDouble();
+ rng.Next(); // osu!stable retrieved a random banana type
+ rng.Next(); // osu!stable retrieved a random banana rotation
+ rng.Next(); // osu!stable retrieved a random banana colour
+ }
+ break;
+ case JuiceStream juiceStream:
+ foreach (var nested in juiceStream.NestedHitObjects)
+ {
+ var hitObject = (CatchHitObject)nested;
+ if (hitObject is TinyDroplet)
+ hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
+ else if (hitObject is Droplet)
+ rng.Next(); // osu!stable retrieved a random droplet rotation
+ hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
+ }
+ break;
+ }
+ }
}
private void initialiseHyperDash(List objects)
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
new file mode 100644
index 0000000000..c39e663d75
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -0,0 +1,36 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchBananaJudgement : CatchJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ public override bool ShouldExplode => true;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 1100;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 8;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
new file mode 100644
index 0000000000..0df2305339
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchDropletJudgement : CatchJudgement
+ {
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 30;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 7;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index bb2786f14f..51d7d3b5cd 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -2,11 +2,51 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchJudgement : Judgement
{
- // todo: wangs
+ public override HitResult MaxResult => HitResult.Perfect;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 300;
+ }
+ }
+
+ ///
+ /// The base health increase for the result achieved.
+ ///
+ public float HealthIncrease => HealthIncreaseFor(Result);
+
+ ///
+ /// Whether fruit on the platter should explode or drop.
+ /// Note that this is only checked if the owning object is also
+ ///
+ public virtual bool ShouldExplode => IsHit;
+
+ ///
+ /// Convert a to a base health increase.
+ ///
+ /// The value to convert.
+ /// The base health increase.
+ protected virtual float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 10.2f;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
new file mode 100644
index 0000000000..8b77351027
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchTinyDropletJudgement : CatchJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 10;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 4;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
new file mode 100644
index 0000000000..5b3835755a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -0,0 +1,91 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+
+namespace osu.Game.Rulesets.Catch.MathUtils
+{
+ ///
+ /// A PRNG specified in http://heliosphan.org/fastrandom.html.
+ ///
+ public class FastRandom
+ {
+ private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
+ private const uint int_mask = 0x7FFFFFFF;
+ private const uint y = 842502087;
+ private const uint z = 3579807591;
+ private const uint w = 273326509;
+ private uint _x, _y = y, _z = z, _w = w;
+
+ public FastRandom(int seed)
+ {
+ _x = (uint)seed;
+ }
+
+ public FastRandom()
+ : this(Environment.TickCount)
+ {
+ }
+
+ ///
+ /// Generates a random unsigned integer within the range [, ).
+ ///
+ /// The random value.
+ public uint NextUInt()
+ {
+ uint t = _x ^ _x << 11;
+ _x = _y;
+ _y = _z;
+ _z = _w;
+ return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
+ }
+
+ ///
+ /// Generates a random integer value within the range [0, ).
+ ///
+ /// The random value.
+ public int Next() => (int)(int_mask & NextUInt());
+
+ ///
+ /// Generates a random integer value within the range [0, ).
+ ///
+ /// The upper bound.
+ /// The random value.
+ public int Next(int upperBound) => (int)(NextDouble() * upperBound);
+
+ ///
+ /// Generates a random integer value within the range [, ).
+ ///
+ /// The lower bound of the range.
+ /// The upper bound of the range.
+ /// The random value.
+ public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+
+ ///
+ /// Generates a random double value within the range [0, 1).
+ ///
+ /// The random value.
+ public double NextDouble() => int_to_real * Next();
+
+ private uint bitBuffer;
+ private int bitIndex = 32;
+
+ ///
+ /// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
+ ///
+ /// The random value.
+ public bool NextBool()
+ {
+ if (bitIndex == 32)
+ {
+ bitBuffer = NextUInt();
+ bitIndex = 1;
+
+ return (bitBuffer & 1) == 1;
+ }
+
+ bitIndex++;
+ return ((bitBuffer >>= 1) & 1) == 1;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs
new file mode 100644
index 0000000000..f7c60a7a47
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ public class Banana : Fruit
+ {
+ public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index a6aba42f03..25af7e4bdf 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.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 osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
@@ -31,18 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana
{
Samples = Samples,
- StartTime = i,
- X = RNG.NextSingle()
+ StartTime = i
});
}
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
-
- public class Banana : Fruit
- {
- public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
new file mode 100644
index 0000000000..dd027abbe0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Catch.Judgements;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableBanana : DrawableFruit
+ {
+ public DrawableBanana(Banana h)
+ : base(h)
+ {
+ }
+
+ protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
index 739cc6a59b..f039504600 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
@@ -5,9 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
- foreach (var b in s.NestedHitObjects.Cast())
+ foreach (var b in s.NestedHitObjects.Cast())
AddNested(getVisualRepresentation?.Invoke(b));
}
- protected override void CheckForJudgements(bool userTriggered, double timeOffset)
- {
- if (timeOffset >= 0)
- AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
- }
+ protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index e3564b5967..6ce2e6a2ae 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -2,14 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using OpenTK;
+using OpenTK.Graphics;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
-using OpenTK;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -58,9 +58,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (CheckPosition == null) return;
if (timeOffset >= 0)
- AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
+ {
+ var judgement = CreateJudgement();
+ judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss;
+ AddJudgement(judgement);
+ }
}
+ protected virtual CatchJudgement CreateJudgement() => new CatchJudgement();
+
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index 5c8a7c4a7c..11d5ed1f92 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
+using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Masking = false;
}
+ protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement();
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
new file mode 100644
index 0000000000..2232bb81a7
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Game.Rulesets.Catch.Judgements;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableTinyDroplet : DrawableDroplet
+ {
+ public DrawableTinyDroplet(Droplet h)
+ : base(h)
+ {
+ Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
+ }
+
+ protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index b2d8e3f8a5..0344189af5 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
-
createTicks();
}
@@ -78,6 +77,13 @@ namespace osu.Game.Rulesets.Catch.Objects
double time = spanStartTime + timeProgress * spanDuration;
+ if (LegacyLastTickOffset != null)
+ {
+ // If we're the last tick, apply the legacy offset
+ if (span == this.SpanCount() - 1 && d + tickDistance > length)
+ time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
+ }
+
double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
@@ -124,9 +130,6 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
-
- if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
- lastNested.LastInCombo = LastInCombo;
}
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
@@ -156,5 +159,7 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
+
+ public double? LegacyLastTickOffset { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 936ab6a9d3..23b620248f 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
return;
}
- if (h is BananaShower.Banana)
+ if (h is Banana)
{
// auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
@@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
switch (nestedObj)
{
- case BananaShower.Banana _:
+ case Banana _:
case TinyDroplet _:
case Droplet _:
case Fruit _:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
index 9357d3b75c..b65d54a565 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -1,957 +1,1275 @@
{
"Mappings": [{
- "StartTime": 500.0,
- "Objects": [{
- "StartTime": 500.0,
- "Position": 96.0
- }, {
- "StartTime": 562.0,
- "Position": 100.84
- }, {
- "StartTime": 625.0,
- "Position": 125.0
- }, {
- "StartTime": 687.0,
- "Position": 152.84
- }, {
- "StartTime": 750.0,
- "Position": 191.0
- }, {
- "StartTime": 812.0,
- "Position": 212.84
- }, {
- "StartTime": 875.0,
- "Position": 217.0
- }, {
- "StartTime": 937.0,
- "Position": 234.84
- }, {
- "StartTime": 1000.0,
- "Position": 256.0
- }, {
- "StartTime": 1062.0,
- "Position": 267.84
- }, {
- "StartTime": 1125.0,
- "Position": 284.0
- }, {
- "StartTime": 1187.0,
- "Position": 311.84
- }, {
- "StartTime": 1250.0,
- "Position": 350.0
- }, {
- "StartTime": 1312.0,
- "Position": 359.84
- }, {
- "StartTime": 1375.0,
- "Position": 367.0
- }, {
- "StartTime": 1437.0,
- "Position": 400.84
- }, {
- "StartTime": 1500.0,
- "Position": 416.0
- }, {
- "StartTime": 1562.0,
- "Position": 377.159973
- }, {
- "StartTime": 1625.0,
- "Position": 367.0
- }, {
- "StartTime": 1687.0,
- "Position": 374.159973
- }, {
- "StartTime": 1750.0,
- "Position": 353.0
- }, {
- "StartTime": 1812.0,
- "Position": 329.159973
- }, {
- "StartTime": 1875.0,
- "Position": 288.0
- }, {
- "StartTime": 1937.0,
- "Position": 259.159973
- }, {
- "StartTime": 2000.0,
- "Position": 256.0
- }, {
- "StartTime": 2058.0,
- "Position": 232.44
- }, {
- "StartTime": 2116.0,
- "Position": 222.879974
- }, {
- "StartTime": 2174.0,
- "Position": 185.319992
- }, {
- "StartTime": 2232.0,
- "Position": 177.76001
- }, {
- "StartTime": 2290.0,
- "Position": 162.200012
- }, {
- "StartTime": 2348.0,
- "Position": 158.639984
- }, {
- "StartTime": 2406.0,
- "Position": 111.079994
- }, {
- "StartTime": 2500.0,
- "Position": 96.0
- }]
- }, {
- "StartTime": 3000.0,
- "Objects": [{
- "StartTime": 3000.0,
- "Position": 18.0
- }, {
- "StartTime": 3062.0,
- "Position": 482.0
- }, {
- "StartTime": 3125.0,
- "Position": 243.0
- }, {
- "StartTime": 3187.0,
- "Position": 332.0
- }, {
- "StartTime": 3250.0,
- "Position": 477.0
- }, {
- "StartTime": 3312.0,
- "Position": 376.0
- }, {
- "StartTime": 3375.0,
- "Position": 104.0
- }, {
- "StartTime": 3437.0,
- "Position": 156.0
- }, {
- "StartTime": 3500.0,
- "Position": 135.0
- }, {
- "StartTime": 3562.0,
- "Position": 256.0
- }, {
- "StartTime": 3625.0,
- "Position": 360.0
- }, {
- "StartTime": 3687.0,
- "Position": 199.0
- }, {
- "StartTime": 3750.0,
- "Position": 239.0
- }, {
- "StartTime": 3812.0,
- "Position": 326.0
- }, {
- "StartTime": 3875.0,
- "Position": 393.0
- }, {
- "StartTime": 3937.0,
- "Position": 470.0
- }, {
- "StartTime": 4000.0,
- "Position": 136.0
- }]
- }, {
- "StartTime": 4500.0,
- "Objects": [{
- "StartTime": 4500.0,
- "Position": 317.0
- }, {
- "StartTime": 4562.0,
- "Position": 354.0
- }, {
- "StartTime": 4625.0,
- "Position": 414.0
- }, {
- "StartTime": 4687.0,
- "Position": 39.0
- }, {
- "StartTime": 4750.0,
- "Position": 172.0
- }, {
- "StartTime": 4812.0,
- "Position": 479.0
- }, {
- "StartTime": 4875.0,
- "Position": 18.0
- }, {
- "StartTime": 4937.0,
- "Position": 151.0
- }, {
- "StartTime": 5000.0,
- "Position": 342.0
- }, {
- "StartTime": 5062.0,
- "Position": 400.0
- }, {
- "StartTime": 5125.0,
- "Position": 420.0
- }, {
- "StartTime": 5187.0,
- "Position": 90.0
- }, {
- "StartTime": 5250.0,
- "Position": 220.0
- }, {
- "StartTime": 5312.0,
- "Position": 80.0
- }, {
- "StartTime": 5375.0,
- "Position": 421.0
- }, {
- "StartTime": 5437.0,
- "Position": 473.0
- }, {
- "StartTime": 5500.0,
- "Position": 97.0
- }]
- }, {
- "StartTime": 6000.0,
- "Objects": [{
- "StartTime": 6000.0,
- "Position": 105.0
- }, {
- "StartTime": 6062.0,
- "Position": 249.0
- }, {
- "StartTime": 6125.0,
- "Position": 163.0
- }, {
- "StartTime": 6187.0,
- "Position": 194.0
- }, {
- "StartTime": 6250.0,
- "Position": 106.0
- }, {
- "StartTime": 6312.0,
- "Position": 212.0
- }, {
- "StartTime": 6375.0,
- "Position": 257.0
- }, {
- "StartTime": 6437.0,
- "Position": 461.0
- }, {
- "StartTime": 6500.0,
- "Position": 79.0
- }]
- }, {
- "StartTime": 7000.0,
- "Objects": [{
- "StartTime": 7000.0,
- "Position": 256.0
- }, {
- "StartTime": 7062.0,
- "Position": 294.84
- }, {
- "StartTime": 7125.0,
- "Position": 279.0
- }, {
- "StartTime": 7187.0,
- "Position": 309.84
- }, {
- "StartTime": 7250.0,
- "Position": 336.0
- }, {
- "StartTime": 7312.0,
- "Position": 322.16
- }, {
- "StartTime": 7375.0,
- "Position": 308.0
- }, {
- "StartTime": 7437.0,
- "Position": 263.16
- }, {
- "StartTime": 7500.0,
- "Position": 256.0
- }, {
- "StartTime": 7562.0,
- "Position": 261.84
- }, {
- "StartTime": 7625.0,
- "Position": 277.0
- }, {
- "StartTime": 7687.0,
- "Position": 318.84
- }, {
- "StartTime": 7750.0,
- "Position": 336.0
- }, {
- "StartTime": 7803.0,
- "Position": 305.04
- }, {
- "StartTime": 7857.0,
- "Position": 307.76
- }, {
- "StartTime": 7910.0,
- "Position": 297.8
- }, {
- "StartTime": 8000.0,
- "Position": 256.0
- }]
- }, {
- "StartTime": 8500.0,
- "Objects": [{
- "StartTime": 8500.0,
- "Position": 32.0
- }, {
- "StartTime": 8562.0,
- "Position": 22.8515015
- }, {
- "StartTime": 8625.0,
- "Position": 28.5659637
- }, {
- "StartTime": 8687.0,
- "Position": 50.3433228
- }, {
- "StartTime": 8750.0,
- "Position": 56.58974
- }, {
- "StartTime": 8812.0,
- "Position": 64.23422
- }, {
- "StartTime": 8875.0,
- "Position": 67.7117844
- }, {
- "StartTime": 8937.0,
- "Position": 90.52607
- }, {
- "StartTime": 9000.0,
- "Position": 101.81015
- }, {
- "StartTime": 9062.0,
- "Position": 113.478188
- }, {
- "StartTime": 9125.0,
- "Position": 159.414444
- }, {
- "StartTime": 9187.0,
- "Position": 155.1861
- }, {
- "StartTime": 9250.0,
- "Position": 179.600418
- }, {
- "StartTime": 9312.0,
- "Position": 212.293015
- }, {
- "StartTime": 9375.0,
- "Position": 197.2076
- }, {
- "StartTime": 9437.0,
- "Position": 243.438324
- }, {
- "StartTime": 9500.0,
- "Position": 237.2304
- }, {
- "StartTime": 9562.0,
- "Position": 241.253983
- }, {
- "StartTime": 9625.0,
- "Position": 258.950623
- }, {
- "StartTime": 9687.0,
- "Position": 253.3786
- }, {
- "StartTime": 9750.0,
- "Position": 270.8865
- }, {
- "StartTime": 9812.0,
- "Position": 244.38974
- }, {
- "StartTime": 9875.0,
- "Position": 242.701874
- }, {
- "StartTime": 9937.0,
- "Position": 256.2331
- }, {
- "StartTime": 10000.0,
- "Position": 270.339874
- }, {
- "StartTime": 10062.0,
- "Position": 275.9349
- }, {
- "StartTime": 10125.0,
- "Position": 297.2969
- }, {
- "StartTime": 10187.0,
- "Position": 307.834137
- }, {
- "StartTime": 10250.0,
- "Position": 321.6449
- }, {
- "StartTime": 10312.0,
- "Position": 357.746338
- }, {
- "StartTime": 10375.0,
- "Position": 358.21875
- }, {
- "StartTime": 10437.0,
- "Position": 394.943
- }, {
- "StartTime": 10500.0,
- "Position": 401.0588
- }, {
- "StartTime": 10558.0,
- "Position": 418.21347
- }, {
- "StartTime": 10616.0,
- "Position": 424.6034
- }, {
- "StartTime": 10674.0,
- "Position": 455.835754
- }, {
- "StartTime": 10732.0,
- "Position": 477.5042
- }, {
- "StartTime": 10790.0,
- "Position": 476.290955
- }, {
- "StartTime": 10848.0,
- "Position": 470.943237
- }, {
- "StartTime": 10906.0,
- "Position": 503.3372
- }, {
- "StartTime": 10999.0,
- "Position": 508.166229
- }]
- }, {
- "StartTime": 11500.0,
- "Objects": [{
- "StartTime": 11500.0,
- "Position": 321.0
- }, {
- "StartTime": 11562.0,
- "Position": 17.0
- }, {
- "StartTime": 11625.0,
- "Position": 173.0
- }, {
- "StartTime": 11687.0,
- "Position": 170.0
- }, {
- "StartTime": 11750.0,
- "Position": 447.0
- }, {
- "StartTime": 11812.0,
- "Position": 218.0
- }, {
- "StartTime": 11875.0,
- "Position": 394.0
- }, {
- "StartTime": 11937.0,
- "Position": 46.0
- }, {
- "StartTime": 12000.0,
- "Position": 480.0
- }]
- }, {
- "StartTime": 12500.0,
- "Objects": [{
- "StartTime": 12500.0,
- "Position": 512.0
- }, {
- "StartTime": 12562.0,
- "Position": 491.3132
- }, {
- "StartTime": 12625.0,
- "Position": 484.3089
- }, {
- "StartTime": 12687.0,
- "Position": 454.6221
- }, {
- "StartTime": 12750.0,
- "Position": 433.617767
- }, {
- "StartTime": 12812.0,
- "Position": 399.930969
- }, {
- "StartTime": 12875.0,
- "Position": 395.926666
- }, {
- "StartTime": 12937.0,
- "Position": 361.239868
- }, {
- "StartTime": 13000.0,
- "Position": 353.235535
- }, {
- "StartTime": 13062.0,
- "Position": 314.548767
- }, {
- "StartTime": 13125.0,
- "Position": 315.544434
- }, {
- "StartTime": 13187.0,
- "Position": 288.857635
- }, {
- "StartTime": 13250.0,
- "Position": 254.853333
- }, {
- "StartTime": 13312.0,
- "Position": 239.166534
- }, {
- "StartTime": 13375.0,
- "Position": 240.1622
- }, {
- "StartTime": 13437.0,
- "Position": 212.4754
- }, {
- "StartTime": 13500.0,
- "Position": 194.471069
- }, {
- "StartTime": 13562.0,
- "Position": 161.784271
- }, {
- "StartTime": 13625.0,
- "Position": 145.779968
- }, {
- "StartTime": 13687.0,
- "Position": 129.09314
- }, {
- "StartTime": 13750.0,
- "Position": 104.088837
- }, {
- "StartTime": 13812.0,
- "Position": 95.40204
- }, {
- "StartTime": 13875.0,
- "Position": 61.3977356
- }, {
- "StartTime": 13937.0,
- "Position": 56.710907
- }, {
- "StartTime": 14000.0,
- "Position": 35.7066345
- }, {
- "StartTime": 14062.0,
- "Position": 5.019806
- }, {
- "StartTime": 14125.0,
- "Position": 0.0
- }, {
- "StartTime": 14187.0,
- "Position": 39.7696266
- }, {
- "StartTime": 14250.0,
- "Position": 23.0119171
- }, {
- "StartTime": 14312.0,
- "Position": 75.94882
- }, {
- "StartTime": 14375.0,
- "Position": 98.19112
- }, {
- "StartTime": 14437.0,
- "Position": 82.12803
- }, {
- "StartTime": 14500.0,
- "Position": 118.370323
- }, {
- "StartTime": 14562.0,
- "Position": 149.307236
- }, {
- "StartTime": 14625.0,
- "Position": 168.549515
- }, {
- "StartTime": 14687.0,
- "Position": 190.486435
- }, {
- "StartTime": 14750.0,
- "Position": 186.728714
- }, {
- "StartTime": 14812.0,
- "Position": 199.665634
- }, {
- "StartTime": 14875.0,
- "Position": 228.907928
- }, {
- "StartTime": 14937.0,
- "Position": 264.844849
- }, {
- "StartTime": 15000.0,
- "Position": 271.087128
- }, {
- "StartTime": 15062.0,
- "Position": 290.024017
- }, {
- "StartTime": 15125.0,
- "Position": 302.266327
- }, {
- "StartTime": 15187.0,
- "Position": 344.203247
- }, {
- "StartTime": 15250.0,
- "Position": 356.445526
- }, {
- "StartTime": 15312.0,
- "Position": 359.382446
- }, {
- "StartTime": 15375.0,
- "Position": 401.624725
- }, {
- "StartTime": 15437.0,
- "Position": 388.561646
- }, {
- "StartTime": 15500.0,
- "Position": 423.803925
- }, {
- "StartTime": 15562.0,
- "Position": 425.740845
- }, {
- "StartTime": 15625.0,
- "Position": 449.983124
- }, {
- "StartTime": 15687.0,
- "Position": 468.920044
- }, {
- "StartTime": 15750.0,
- "Position": 492.162323
- }, {
- "StartTime": 15812.0,
- "Position": 506.784332
- }, {
- "StartTime": 15875.0,
- "Position": 474.226227
- }, {
- "StartTime": 15937.0,
- "Position": 482.978638
- }, {
- "StartTime": 16000.0,
- "Position": 446.420532
- }, {
- "StartTime": 16058.0,
- "Position": 418.4146
- }, {
- "StartTime": 16116.0,
- "Position": 425.408844
- }, {
- "StartTime": 16174.0,
- "Position": 383.402924
- }, {
- "StartTime": 16232.0,
- "Position": 363.397156
- }, {
- "StartTime": 16290.0,
- "Position": 343.391235
- }, {
- "StartTime": 16348.0,
- "Position": 328.385468
- }, {
- "StartTime": 16406.0,
- "Position": 322.3797
- }, {
- "StartTime": 16500.0,
- "Position": 291.1977
- }]
- }, {
- "StartTime": 17000.0,
- "Objects": [{
- "StartTime": 17000.0,
- "Position": 256.0
- }, {
- "StartTime": 17062.0,
- "Position": 228.16
- }, {
- "StartTime": 17125.0,
- "Position": 234.0
- }, {
- "StartTime": 17187.0,
- "Position": 202.16
- }, {
- "StartTime": 17250.0,
- "Position": 176.0
- }, {
- "StartTime": 17312.0,
- "Position": 210.84
- }, {
- "StartTime": 17375.0,
- "Position": 221.0
- }, {
- "StartTime": 17437.0,
- "Position": 219.84
- }, {
- "StartTime": 17500.0,
- "Position": 256.0
- }, {
- "StartTime": 17562.0,
- "Position": 219.16
- }, {
- "StartTime": 17625.0,
- "Position": 228.0
- }, {
- "StartTime": 17687.0,
- "Position": 203.16
- }, {
- "StartTime": 17750.0,
- "Position": 176.0
- }, {
- "StartTime": 17803.0,
- "Position": 174.959991
- }, {
- "StartTime": 17857.0,
- "Position": 214.23999
- }, {
- "StartTime": 17910.0,
- "Position": 228.200012
- }, {
- "StartTime": 18000.0,
- "Position": 256.0
- }]
- }, {
- "StartTime": 18500.0,
- "Objects": [{
- "StartTime": 18500.0,
- "Position": 362.0
- }, {
- "StartTime": 18559.0,
- "Position": 249.0
- }, {
- "StartTime": 18618.0,
- "Position": 357.0
- }, {
- "StartTime": 18678.0,
- "Position": 167.0
- }, {
- "StartTime": 18737.0,
- "Position": 477.0
- }, {
- "StartTime": 18796.0,
- "Position": 411.0
- }, {
- "StartTime": 18856.0,
- "Position": 254.0
- }, {
- "StartTime": 18915.0,
- "Position": 308.0
- }, {
- "StartTime": 18975.0,
- "Position": 399.0
- }, {
- "StartTime": 19034.0,
- "Position": 176.0
- }, {
- "StartTime": 19093.0,
- "Position": 14.0
- }, {
- "StartTime": 19153.0,
- "Position": 258.0
- }, {
- "StartTime": 19212.0,
- "Position": 221.0
- }, {
- "StartTime": 19271.0,
- "Position": 481.0
- }, {
- "StartTime": 19331.0,
- "Position": 92.0
- }, {
- "StartTime": 19390.0,
- "Position": 211.0
- }, {
- "StartTime": 19450.0,
- "Position": 135.0
- }]
- }, {
- "StartTime": 19875.0,
- "Objects": [{
- "StartTime": 19875.0,
- "Position": 216.0
- }, {
- "StartTime": 19937.0,
- "Position": 215.307053
- }, {
- "StartTime": 20000.0,
- "Position": 236.036865
- }, {
- "StartTime": 20062.0,
- "Position": 236.312088
- }, {
- "StartTime": 20125.0,
- "Position": 235.838928
- }, {
- "StartTime": 20187.0,
- "Position": 269.9743
- }, {
- "StartTime": 20250.0,
- "Position": 285.999146
- }, {
- "StartTime": 20312.0,
- "Position": 283.669067
- }, {
- "StartTime": 20375.0,
- "Position": 317.446747
- }, {
- "StartTime": 20437.0,
- "Position": 330.750275
- }, {
- "StartTime": 20500.0,
- "Position": 344.0156
- }, {
- "StartTime": 20562.0,
- "Position": 318.472168
- }, {
- "StartTime": 20625.0,
- "Position": 309.165466
- }, {
- "StartTime": 20687.0,
- "Position": 317.044617
- }, {
- "StartTime": 20750.0,
- "Position": 280.457367
- }, {
- "StartTime": 20812.0,
- "Position": 272.220581
- }, {
- "StartTime": 20875.0,
- "Position": 270.3294
- }, {
- "StartTime": 20937.0,
- "Position": 262.57605
- }, {
- "StartTime": 21000.0,
- "Position": 244.803329
- }, {
- "StartTime": 21062.0,
- "Position": 215.958359
- }, {
- "StartTime": 21125.0,
- "Position": 177.79332
- }, {
- "StartTime": 21187.0,
- "Position": 190.948349
- }, {
- "StartTime": 21250.0,
- "Position": 158.78334
- }, {
- "StartTime": 21312.0,
- "Position": 136.93837
- }, {
- "StartTime": 21375.0,
- "Position": 119.121056
- }, {
- "StartTime": 21437.0,
- "Position": 132.387573
- }, {
- "StartTime": 21500.0,
- "Position": 124.503014
- }, {
- "StartTime": 21562.0,
- "Position": 118.749374
- }, {
- "StartTime": 21625.0,
- "Position": 123.165535
- }, {
- "StartTime": 21687.0,
- "Position": 96.02999
- }, {
- "StartTime": 21750.0,
- "Position": 118.547928
- }, {
- "StartTime": 21812.0,
- "Position": 128.856232
- }, {
- "StartTime": 21875.0,
- "Position": 124.28746
- }, {
- "StartTime": 21937.0,
- "Position": 150.754929
- }, {
- "StartTime": 22000.0,
- "Position": 149.528732
- }, {
- "StartTime": 22062.0,
- "Position": 145.1691
- }, {
- "StartTime": 22125.0,
- "Position": 182.802155
- }, {
- "StartTime": 22187.0,
- "Position": 178.6452
- }, {
- "StartTime": 22250.0,
- "Position": 213.892181
- }, {
- "StartTime": 22312.0,
- "Position": 218.713028
- }, {
- "StartTime": 22375.0,
- "Position": 240.4715
- }, {
- "StartTime": 22437.0,
- "Position": 239.371887
- }, {
- "StartTime": 22500.0,
- "Position": 261.907257
- }, {
- "StartTime": 22562.0,
- "Position": 314.353119
- }, {
- "StartTime": 22625.0,
- "Position": 299.273376
- }, {
- "StartTime": 22687.0,
- "Position": 356.98288
- }, {
- "StartTime": 22750.0,
- "Position": 339.078552
- }, {
- "StartTime": 22812.0,
- "Position": 377.8958
- }, {
- "StartTime": 22875.0,
- "Position": 398.054047
- }, {
- "StartTime": 22937.0,
- "Position": 398.739441
- }, {
- "StartTime": 23000.0,
- "Position": 407.178467
- }, {
- "StartTime": 23062.0,
- "Position": 444.8687
- }, {
- "StartTime": 23125.0,
- "Position": 417.069977
- }, {
- "StartTime": 23187.0,
- "Position": 454.688477
- }, {
- "StartTime": 23250.0,
- "Position": 428.9612
- }, {
- "StartTime": 23312.0,
- "Position": 441.92807
- }, {
- "StartTime": 23375.0,
- "Position": 439.749878
- }, {
- "StartTime": 23433.0,
- "Position": 455.644684
- }, {
- "StartTime": 23491.0,
- "Position": 440.7359
- }, {
- "StartTime": 23549.0,
- "Position": 430.0944
- }, {
- "StartTime": 23607.0,
- "Position": 420.796173
- }, {
- "StartTime": 23665.0,
- "Position": 435.897461
- }, {
- "StartTime": 23723.0,
- "Position": 418.462555
- }, {
- "StartTime": 23781.0,
- "Position": 405.53775
- }, {
- "StartTime": 23874.0,
- "Position": 408.720825
- }]
- }]
+ "StartTime": 500,
+ "Objects": [{
+ "StartTime": 500,
+ "Position": 96
+ },
+ {
+ "StartTime": 562,
+ "Position": 100.84
+ },
+ {
+ "StartTime": 625,
+ "Position": 125
+ },
+ {
+ "StartTime": 687,
+ "Position": 152.84
+ },
+ {
+ "StartTime": 750,
+ "Position": 191
+ },
+ {
+ "StartTime": 812,
+ "Position": 212.84
+ },
+ {
+ "StartTime": 875,
+ "Position": 217
+ },
+ {
+ "StartTime": 937,
+ "Position": 234.84
+ },
+ {
+ "StartTime": 1000,
+ "Position": 256
+ },
+ {
+ "StartTime": 1062,
+ "Position": 267.84
+ },
+ {
+ "StartTime": 1125,
+ "Position": 284
+ },
+ {
+ "StartTime": 1187,
+ "Position": 311.84
+ },
+ {
+ "StartTime": 1250,
+ "Position": 350
+ },
+ {
+ "StartTime": 1312,
+ "Position": 359.84
+ },
+ {
+ "StartTime": 1375,
+ "Position": 367
+ },
+ {
+ "StartTime": 1437,
+ "Position": 400.84
+ },
+ {
+ "StartTime": 1500,
+ "Position": 416
+ },
+ {
+ "StartTime": 1562,
+ "Position": 377.159973
+ },
+ {
+ "StartTime": 1625,
+ "Position": 367
+ },
+ {
+ "StartTime": 1687,
+ "Position": 374.159973
+ },
+ {
+ "StartTime": 1750,
+ "Position": 353
+ },
+ {
+ "StartTime": 1812,
+ "Position": 329.159973
+ },
+ {
+ "StartTime": 1875,
+ "Position": 288
+ },
+ {
+ "StartTime": 1937,
+ "Position": 259.159973
+ },
+ {
+ "StartTime": 2000,
+ "Position": 256
+ },
+ {
+ "StartTime": 2058,
+ "Position": 232.44
+ },
+ {
+ "StartTime": 2116,
+ "Position": 222.879974
+ },
+ {
+ "StartTime": 2174,
+ "Position": 185.319992
+ },
+ {
+ "StartTime": 2232,
+ "Position": 177.76001
+ },
+ {
+ "StartTime": 2290,
+ "Position": 162.200012
+ },
+ {
+ "StartTime": 2348,
+ "Position": 158.639984
+ },
+ {
+ "StartTime": 2406,
+ "Position": 111.079994
+ },
+ {
+ "StartTime": 2500,
+ "Position": 96
+ }
+ ]
+ },
+ {
+ "StartTime": 3000,
+ "Objects": [{
+ "StartTime": 3000,
+ "Position": 18
+ },
+ {
+ "StartTime": 3062,
+ "Position": 249
+ },
+ {
+ "StartTime": 3125,
+ "Position": 184
+ },
+ {
+ "StartTime": 3187,
+ "Position": 477
+ },
+ {
+ "StartTime": 3250,
+ "Position": 43
+ },
+ {
+ "StartTime": 3312,
+ "Position": 494
+ },
+ {
+ "StartTime": 3375,
+ "Position": 135
+ },
+ {
+ "StartTime": 3437,
+ "Position": 30
+ },
+ {
+ "StartTime": 3500,
+ "Position": 11
+ },
+ {
+ "StartTime": 3562,
+ "Position": 239
+ },
+ {
+ "StartTime": 3625,
+ "Position": 505
+ },
+ {
+ "StartTime": 3687,
+ "Position": 353
+ },
+ {
+ "StartTime": 3750,
+ "Position": 136
+ },
+ {
+ "StartTime": 3812,
+ "Position": 135
+ },
+ {
+ "StartTime": 3875,
+ "Position": 346
+ },
+ {
+ "StartTime": 3937,
+ "Position": 39
+ },
+ {
+ "StartTime": 4000,
+ "Position": 300
+ }
+ ]
+ },
+ {
+ "StartTime": 4500,
+ "Objects": [{
+ "StartTime": 4500,
+ "Position": 398
+ },
+ {
+ "StartTime": 4562,
+ "Position": 151
+ },
+ {
+ "StartTime": 4625,
+ "Position": 73
+ },
+ {
+ "StartTime": 4687,
+ "Position": 311
+ },
+ {
+ "StartTime": 4750,
+ "Position": 90
+ },
+ {
+ "StartTime": 4812,
+ "Position": 264
+ },
+ {
+ "StartTime": 4875,
+ "Position": 477
+ },
+ {
+ "StartTime": 4937,
+ "Position": 473
+ },
+ {
+ "StartTime": 5000,
+ "Position": 120
+ },
+ {
+ "StartTime": 5062,
+ "Position": 115
+ },
+ {
+ "StartTime": 5125,
+ "Position": 163
+ },
+ {
+ "StartTime": 5187,
+ "Position": 447
+ },
+ {
+ "StartTime": 5250,
+ "Position": 72
+ },
+ {
+ "StartTime": 5312,
+ "Position": 257
+ },
+ {
+ "StartTime": 5375,
+ "Position": 153
+ },
+ {
+ "StartTime": 5437,
+ "Position": 388
+ },
+ {
+ "StartTime": 5500,
+ "Position": 336
+ }
+ ]
+ },
+ {
+ "StartTime": 6000,
+ "Objects": [{
+ "StartTime": 6000,
+ "Position": 13
+ },
+ {
+ "StartTime": 6062,
+ "Position": 429
+ },
+ {
+ "StartTime": 6125,
+ "Position": 381
+ },
+ {
+ "StartTime": 6187,
+ "Position": 186
+ },
+ {
+ "StartTime": 6250,
+ "Position": 267
+ },
+ {
+ "StartTime": 6312,
+ "Position": 305
+ },
+ {
+ "StartTime": 6375,
+ "Position": 456
+ },
+ {
+ "StartTime": 6437,
+ "Position": 26
+ },
+ {
+ "StartTime": 6500,
+ "Position": 238
+ }
+ ]
+ },
+ {
+ "StartTime": 7000,
+ "Objects": [{
+ "StartTime": 7000,
+ "Position": 256
+ },
+ {
+ "StartTime": 7062,
+ "Position": 262.84
+ },
+ {
+ "StartTime": 7125,
+ "Position": 295
+ },
+ {
+ "StartTime": 7187,
+ "Position": 303.84
+ },
+ {
+ "StartTime": 7250,
+ "Position": 336
+ },
+ {
+ "StartTime": 7312,
+ "Position": 319.16
+ },
+ {
+ "StartTime": 7375,
+ "Position": 306
+ },
+ {
+ "StartTime": 7437,
+ "Position": 272.16
+ },
+ {
+ "StartTime": 7500,
+ "Position": 256
+ },
+ {
+ "StartTime": 7562,
+ "Position": 255.84
+ },
+ {
+ "StartTime": 7625,
+ "Position": 300
+ },
+ {
+ "StartTime": 7687,
+ "Position": 320.84
+ },
+ {
+ "StartTime": 7750,
+ "Position": 336
+ },
+ {
+ "StartTime": 7803,
+ "Position": 319.04
+ },
+ {
+ "StartTime": 7857,
+ "Position": 283.76
+ },
+ {
+ "StartTime": 7910,
+ "Position": 265.8
+ },
+ {
+ "StartTime": 8000,
+ "Position": 256
+ }
+ ]
+ },
+ {
+ "StartTime": 8500,
+ "Objects": [{
+ "StartTime": 8500,
+ "Position": 32
+ },
+ {
+ "StartTime": 8562,
+ "Position": 21.8515015
+ },
+ {
+ "StartTime": 8625,
+ "Position": 44.5659637
+ },
+ {
+ "StartTime": 8687,
+ "Position": 33.3433228
+ },
+ {
+ "StartTime": 8750,
+ "Position": 63.58974
+ },
+ {
+ "StartTime": 8812,
+ "Position": 71.23422
+ },
+ {
+ "StartTime": 8875,
+ "Position": 62.7117844
+ },
+ {
+ "StartTime": 8937,
+ "Position": 65.52607
+ },
+ {
+ "StartTime": 9000,
+ "Position": 101.81015
+ },
+ {
+ "StartTime": 9062,
+ "Position": 134.47818
+ },
+ {
+ "StartTime": 9125,
+ "Position": 141.414444
+ },
+ {
+ "StartTime": 9187,
+ "Position": 164.1861
+ },
+ {
+ "StartTime": 9250,
+ "Position": 176.600418
+ },
+ {
+ "StartTime": 9312,
+ "Position": 184.293015
+ },
+ {
+ "StartTime": 9375,
+ "Position": 212.2076
+ },
+ {
+ "StartTime": 9437,
+ "Position": 236.438324
+ },
+ {
+ "StartTime": 9500,
+ "Position": 237.2304
+ },
+ {
+ "StartTime": 9562,
+ "Position": 241.253983
+ },
+ {
+ "StartTime": 9625,
+ "Position": 233.950623
+ },
+ {
+ "StartTime": 9687,
+ "Position": 265.3786
+ },
+ {
+ "StartTime": 9750,
+ "Position": 236.8865
+ },
+ {
+ "StartTime": 9812,
+ "Position": 273.38974
+ },
+ {
+ "StartTime": 9875,
+ "Position": 267.701874
+ },
+ {
+ "StartTime": 9937,
+ "Position": 263.2331
+ },
+ {
+ "StartTime": 10000,
+ "Position": 270.339874
+ },
+ {
+ "StartTime": 10062,
+ "Position": 291.9349
+ },
+ {
+ "StartTime": 10125,
+ "Position": 294.2969
+ },
+ {
+ "StartTime": 10187,
+ "Position": 307.834137
+ },
+ {
+ "StartTime": 10250,
+ "Position": 310.6449
+ },
+ {
+ "StartTime": 10312,
+ "Position": 344.746338
+ },
+ {
+ "StartTime": 10375,
+ "Position": 349.21875
+ },
+ {
+ "StartTime": 10437,
+ "Position": 373.943
+ },
+ {
+ "StartTime": 10500,
+ "Position": 401.0588
+ },
+ {
+ "StartTime": 10558,
+ "Position": 421.21347
+ },
+ {
+ "StartTime": 10616,
+ "Position": 431.6034
+ },
+ {
+ "StartTime": 10674,
+ "Position": 433.835754
+ },
+ {
+ "StartTime": 10732,
+ "Position": 452.5042
+ },
+ {
+ "StartTime": 10790,
+ "Position": 486.290955
+ },
+ {
+ "StartTime": 10848,
+ "Position": 488.943237
+ },
+ {
+ "StartTime": 10906,
+ "Position": 493.3372
+ },
+ {
+ "StartTime": 10999,
+ "Position": 508.166229
+ }
+ ]
+ },
+ {
+ "StartTime": 11500,
+ "Objects": [{
+ "StartTime": 11500,
+ "Position": 97
+ },
+ {
+ "StartTime": 11562,
+ "Position": 267
+ },
+ {
+ "StartTime": 11625,
+ "Position": 116
+ },
+ {
+ "StartTime": 11687,
+ "Position": 451
+ },
+ {
+ "StartTime": 11750,
+ "Position": 414
+ },
+ {
+ "StartTime": 11812,
+ "Position": 88
+ },
+ {
+ "StartTime": 11875,
+ "Position": 257
+ },
+ {
+ "StartTime": 11937,
+ "Position": 175
+ },
+ {
+ "StartTime": 12000,
+ "Position": 38
+ }
+ ]
+ },
+ {
+ "StartTime": 12500,
+ "Objects": [{
+ "StartTime": 12500,
+ "Position": 512
+ },
+ {
+ "StartTime": 12562,
+ "Position": 494.3132
+ },
+ {
+ "StartTime": 12625,
+ "Position": 461.3089
+ },
+ {
+ "StartTime": 12687,
+ "Position": 469.6221
+ },
+ {
+ "StartTime": 12750,
+ "Position": 441.617767
+ },
+ {
+ "StartTime": 12812,
+ "Position": 402.930969
+ },
+ {
+ "StartTime": 12875,
+ "Position": 407.926666
+ },
+ {
+ "StartTime": 12937,
+ "Position": 364.239868
+ },
+ {
+ "StartTime": 13000,
+ "Position": 353.235535
+ },
+ {
+ "StartTime": 13062,
+ "Position": 320.548767
+ },
+ {
+ "StartTime": 13125,
+ "Position": 303.544434
+ },
+ {
+ "StartTime": 13187,
+ "Position": 295.857635
+ },
+ {
+ "StartTime": 13250,
+ "Position": 265.853333
+ },
+ {
+ "StartTime": 13312,
+ "Position": 272.166534
+ },
+ {
+ "StartTime": 13375,
+ "Position": 240.1622
+ },
+ {
+ "StartTime": 13437,
+ "Position": 229.4754
+ },
+ {
+ "StartTime": 13500,
+ "Position": 194.471069
+ },
+ {
+ "StartTime": 13562,
+ "Position": 158.784271
+ },
+ {
+ "StartTime": 13625,
+ "Position": 137.779968
+ },
+ {
+ "StartTime": 13687,
+ "Position": 147.09314
+ },
+ {
+ "StartTime": 13750,
+ "Position": 122.088837
+ },
+ {
+ "StartTime": 13812,
+ "Position": 77.40204
+ },
+ {
+ "StartTime": 13875,
+ "Position": 79.3977356
+ },
+ {
+ "StartTime": 13937,
+ "Position": 56.710907
+ },
+ {
+ "StartTime": 14000,
+ "Position": 35.7066345
+ },
+ {
+ "StartTime": 14062,
+ "Position": 1.01980591
+ },
+ {
+ "StartTime": 14125,
+ "Position": 0
+ },
+ {
+ "StartTime": 14187,
+ "Position": 21.7696266
+ },
+ {
+ "StartTime": 14250,
+ "Position": 49.0119171
+ },
+ {
+ "StartTime": 14312,
+ "Position": 48.9488258
+ },
+ {
+ "StartTime": 14375,
+ "Position": 87.19112
+ },
+ {
+ "StartTime": 14437,
+ "Position": 97.12803
+ },
+ {
+ "StartTime": 14500,
+ "Position": 118.370323
+ },
+ {
+ "StartTime": 14562,
+ "Position": 130.307236
+ },
+ {
+ "StartTime": 14625,
+ "Position": 154.549515
+ },
+ {
+ "StartTime": 14687,
+ "Position": 190.486435
+ },
+ {
+ "StartTime": 14750,
+ "Position": 211.728714
+ },
+ {
+ "StartTime": 14812,
+ "Position": 197.665634
+ },
+ {
+ "StartTime": 14875,
+ "Position": 214.907928
+ },
+ {
+ "StartTime": 14937,
+ "Position": 263.844849
+ },
+ {
+ "StartTime": 15000,
+ "Position": 271.087128
+ },
+ {
+ "StartTime": 15062,
+ "Position": 270.024017
+ },
+ {
+ "StartTime": 15125,
+ "Position": 308.266327
+ },
+ {
+ "StartTime": 15187,
+ "Position": 313.203247
+ },
+ {
+ "StartTime": 15250,
+ "Position": 328.445526
+ },
+ {
+ "StartTime": 15312,
+ "Position": 370.382446
+ },
+ {
+ "StartTime": 15375,
+ "Position": 387.624725
+ },
+ {
+ "StartTime": 15437,
+ "Position": 421.561646
+ },
+ {
+ "StartTime": 15500,
+ "Position": 423.803925
+ },
+ {
+ "StartTime": 15562,
+ "Position": 444.740845
+ },
+ {
+ "StartTime": 15625,
+ "Position": 469.983124
+ },
+ {
+ "StartTime": 15687,
+ "Position": 473.920044
+ },
+ {
+ "StartTime": 15750,
+ "Position": 501.162323
+ },
+ {
+ "StartTime": 15812,
+ "Position": 488.784332
+ },
+ {
+ "StartTime": 15875,
+ "Position": 466.226227
+ },
+ {
+ "StartTime": 15937,
+ "Position": 445.978638
+ },
+ {
+ "StartTime": 16000,
+ "Position": 446.420532
+ },
+ {
+ "StartTime": 16058,
+ "Position": 428.4146
+ },
+ {
+ "StartTime": 16116,
+ "Position": 420.408844
+ },
+ {
+ "StartTime": 16174,
+ "Position": 374.402924
+ },
+ {
+ "StartTime": 16232,
+ "Position": 371.397156
+ },
+ {
+ "StartTime": 16290,
+ "Position": 350.391235
+ },
+ {
+ "StartTime": 16348,
+ "Position": 340.385468
+ },
+ {
+ "StartTime": 16406,
+ "Position": 337.3797
+ },
+ {
+ "StartTime": 16500,
+ "Position": 291.1977
+ }
+ ]
+ },
+ {
+ "StartTime": 17000,
+ "Objects": [{
+ "StartTime": 17000,
+ "Position": 256
+ },
+ {
+ "StartTime": 17062,
+ "Position": 247.16
+ },
+ {
+ "StartTime": 17125,
+ "Position": 211
+ },
+ {
+ "StartTime": 17187,
+ "Position": 183.16
+ },
+ {
+ "StartTime": 17250,
+ "Position": 176
+ },
+ {
+ "StartTime": 17312,
+ "Position": 204.84
+ },
+ {
+ "StartTime": 17375,
+ "Position": 218
+ },
+ {
+ "StartTime": 17437,
+ "Position": 231.84
+ },
+ {
+ "StartTime": 17500,
+ "Position": 256
+ },
+ {
+ "StartTime": 17562,
+ "Position": 229.16
+ },
+ {
+ "StartTime": 17625,
+ "Position": 227
+ },
+ {
+ "StartTime": 17687,
+ "Position": 186.16
+ },
+ {
+ "StartTime": 17750,
+ "Position": 176
+ },
+ {
+ "StartTime": 17803,
+ "Position": 211.959991
+ },
+ {
+ "StartTime": 17857,
+ "Position": 197.23999
+ },
+ {
+ "StartTime": 17910,
+ "Position": 225.200012
+ },
+ {
+ "StartTime": 18000,
+ "Position": 256
+ }
+ ]
+ },
+ {
+ "StartTime": 18500,
+ "Objects": [{
+ "StartTime": 18500,
+ "Position": 437
+ },
+ {
+ "StartTime": 18559,
+ "Position": 289
+ },
+ {
+ "StartTime": 18618,
+ "Position": 464
+ },
+ {
+ "StartTime": 18678,
+ "Position": 36
+ },
+ {
+ "StartTime": 18737,
+ "Position": 378
+ },
+ {
+ "StartTime": 18796,
+ "Position": 297
+ },
+ {
+ "StartTime": 18856,
+ "Position": 418
+ },
+ {
+ "StartTime": 18915,
+ "Position": 329
+ },
+ {
+ "StartTime": 18975,
+ "Position": 338
+ },
+ {
+ "StartTime": 19034,
+ "Position": 394
+ },
+ {
+ "StartTime": 19093,
+ "Position": 40
+ },
+ {
+ "StartTime": 19153,
+ "Position": 13
+ },
+ {
+ "StartTime": 19212,
+ "Position": 80
+ },
+ {
+ "StartTime": 19271,
+ "Position": 138
+ },
+ {
+ "StartTime": 19331,
+ "Position": 311
+ },
+ {
+ "StartTime": 19390,
+ "Position": 216
+ },
+ {
+ "StartTime": 19450,
+ "Position": 310
+ }
+ ]
+ },
+ {
+ "StartTime": 19875,
+ "Objects": [{
+ "StartTime": 19875,
+ "Position": 216
+ },
+ {
+ "StartTime": 19937,
+ "Position": 228.307053
+ },
+ {
+ "StartTime": 20000,
+ "Position": 214.036865
+ },
+ {
+ "StartTime": 20062,
+ "Position": 224.312088
+ },
+ {
+ "StartTime": 20125,
+ "Position": 253.838928
+ },
+ {
+ "StartTime": 20187,
+ "Position": 259.9743
+ },
+ {
+ "StartTime": 20250,
+ "Position": 299.999146
+ },
+ {
+ "StartTime": 20312,
+ "Position": 289.669067
+ },
+ {
+ "StartTime": 20375,
+ "Position": 317.446747
+ },
+ {
+ "StartTime": 20437,
+ "Position": 344.750275
+ },
+ {
+ "StartTime": 20500,
+ "Position": 328.0156
+ },
+ {
+ "StartTime": 20562,
+ "Position": 331.472168
+ },
+ {
+ "StartTime": 20625,
+ "Position": 302.165466
+ },
+ {
+ "StartTime": 20687,
+ "Position": 303.044617
+ },
+ {
+ "StartTime": 20750,
+ "Position": 306.457367
+ },
+ {
+ "StartTime": 20812,
+ "Position": 265.220581
+ },
+ {
+ "StartTime": 20875,
+ "Position": 270.3294
+ },
+ {
+ "StartTime": 20937,
+ "Position": 257.57605
+ },
+ {
+ "StartTime": 21000,
+ "Position": 247.803329
+ },
+ {
+ "StartTime": 21062,
+ "Position": 225.958359
+ },
+ {
+ "StartTime": 21125,
+ "Position": 201.79332
+ },
+ {
+ "StartTime": 21187,
+ "Position": 170.948349
+ },
+ {
+ "StartTime": 21250,
+ "Position": 146.78334
+ },
+ {
+ "StartTime": 21312,
+ "Position": 149.93837
+ },
+ {
+ "StartTime": 21375,
+ "Position": 119.121056
+ },
+ {
+ "StartTime": 21437,
+ "Position": 133.387573
+ },
+ {
+ "StartTime": 21500,
+ "Position": 117.503014
+ },
+ {
+ "StartTime": 21562,
+ "Position": 103.749374
+ },
+ {
+ "StartTime": 21625,
+ "Position": 127.165535
+ },
+ {
+ "StartTime": 21687,
+ "Position": 113.029991
+ },
+ {
+ "StartTime": 21750,
+ "Position": 101.547928
+ },
+ {
+ "StartTime": 21812,
+ "Position": 133.856232
+ },
+ {
+ "StartTime": 21875,
+ "Position": 124.28746
+ },
+ {
+ "StartTime": 21937,
+ "Position": 121.754929
+ },
+ {
+ "StartTime": 22000,
+ "Position": 155.528732
+ },
+ {
+ "StartTime": 22062,
+ "Position": 142.1691
+ },
+ {
+ "StartTime": 22125,
+ "Position": 186.802155
+ },
+ {
+ "StartTime": 22187,
+ "Position": 198.6452
+ },
+ {
+ "StartTime": 22250,
+ "Position": 191.892181
+ },
+ {
+ "StartTime": 22312,
+ "Position": 232.713028
+ },
+ {
+ "StartTime": 22375,
+ "Position": 240.4715
+ },
+ {
+ "StartTime": 22437,
+ "Position": 278.3719
+ },
+ {
+ "StartTime": 22500,
+ "Position": 288.907257
+ },
+ {
+ "StartTime": 22562,
+ "Position": 297.353119
+ },
+ {
+ "StartTime": 22625,
+ "Position": 301.273376
+ },
+ {
+ "StartTime": 22687,
+ "Position": 339.98288
+ },
+ {
+ "StartTime": 22750,
+ "Position": 353.078552
+ },
+ {
+ "StartTime": 22812,
+ "Position": 363.8958
+ },
+ {
+ "StartTime": 22875,
+ "Position": 398.054047
+ },
+ {
+ "StartTime": 22937,
+ "Position": 419.739441
+ },
+ {
+ "StartTime": 23000,
+ "Position": 435.178467
+ },
+ {
+ "StartTime": 23062,
+ "Position": 420.8687
+ },
+ {
+ "StartTime": 23125,
+ "Position": 448.069977
+ },
+ {
+ "StartTime": 23187,
+ "Position": 425.688477
+ },
+ {
+ "StartTime": 23250,
+ "Position": 426.9612
+ },
+ {
+ "StartTime": 23312,
+ "Position": 454.92807
+ },
+ {
+ "StartTime": 23375,
+ "Position": 439.749878
+ },
+ {
+ "StartTime": 23433,
+ "Position": 440.644684
+ },
+ {
+ "StartTime": 23491,
+ "Position": 445.7359
+ },
+ {
+ "StartTime": 23549,
+ "Position": 432.0944
+ },
+ {
+ "StartTime": 23607,
+ "Position": 415.796173
+ },
+ {
+ "StartTime": 23665,
+ "Position": 407.897461
+ },
+ {
+ "StartTime": 23723,
+ "Position": 409.462555
+ },
+ {
+ "StartTime": 23781,
+ "Position": 406.53775
+ },
+ {
+ "StartTime": 23874,
+ "Position": 408.720825
+ }
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json
new file mode 100644
index 0000000000..dd81947f1c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json
@@ -0,0 +1,65 @@
+{
+ "Mappings": [{
+ "StartTime": 2589,
+ "Objects": [{
+ "StartTime": 2589,
+ "Position": 256
+ }]
+ },
+ {
+ "StartTime": 2915,
+ "Objects": [{
+ "StartTime": 2915,
+ "Position": 65
+ },
+ {
+ "StartTime": 2916,
+ "Position": 482
+ }
+ ]
+ },
+ {
+ "StartTime": 3078,
+ "Objects": [{
+ "StartTime": 3078,
+ "Position": 164
+ },
+ {
+ "StartTime": 3079,
+ "Position": 315
+ }
+ ]
+ },
+ {
+ "StartTime": 3241,
+ "Objects": [{
+ "StartTime": 3241,
+ "Position": 145
+ },
+ {
+ "StartTime": 3242,
+ "Position": 159
+ }
+ ]
+ },
+ {
+ "StartTime": 3404,
+ "Objects": [{
+ "StartTime": 3404,
+ "Position": 310
+ },
+ {
+ "StartTime": 3405,
+ "Position": 441
+ }
+ ]
+ },
+ {
+ "StartTime": 5197,
+ "Objects": [{
+ "StartTime": 5197,
+ "Position": 256
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles.osu
new file mode 100644
index 0000000000..9a90768428
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles.osu
@@ -0,0 +1,24 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.7
+Mode: 2
+
+[Difficulty]
+HPDrainRate:5
+CircleSize:2
+OverallDifficulty:5
+ApproachRate:8
+SliderMultiplier:1.4
+SliderTickRate:4
+
+[TimingPoints]
+2589,326.086956521739,4,2,1,70,1,0
+
+[HitObjects]
+256,192,2589,5,0,0:0:0:0:
+256,192,2915,12,0,2916,0:0:0:0:
+256,192,3078,12,0,3079,0:0:0:0:
+256,192,3241,12,0,3242,0:0:0:0:
+256,192,3404,12,0,3405,0:0:0:0:
+256,192,5197,5,0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json
new file mode 100644
index 0000000000..b69b1ae056
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json
@@ -0,0 +1,74 @@
+{
+ "Mappings": [{
+ "StartTime": 18500,
+ "Objects": [{
+ "StartTime": 18500,
+ "Position": 65
+ },
+ {
+ "StartTime": 18559,
+ "Position": 482
+ },
+ {
+ "StartTime": 18618,
+ "Position": 164
+ },
+ {
+ "StartTime": 18678,
+ "Position": 315
+ },
+ {
+ "StartTime": 18737,
+ "Position": 145
+ },
+ {
+ "StartTime": 18796,
+ "Position": 159
+ },
+ {
+ "StartTime": 18856,
+ "Position": 310
+ },
+ {
+ "StartTime": 18915,
+ "Position": 441
+ },
+ {
+ "StartTime": 18975,
+ "Position": 428
+ },
+ {
+ "StartTime": 19034,
+ "Position": 243
+ },
+ {
+ "StartTime": 19093,
+ "Position": 422
+ },
+ {
+ "StartTime": 19153,
+ "Position": 481
+ },
+ {
+ "StartTime": 19212,
+ "Position": 104
+ },
+ {
+ "StartTime": 19271,
+ "Position": 473
+ },
+ {
+ "StartTime": 19331,
+ "Position": 135
+ },
+ {
+ "StartTime": 19390,
+ "Position": 360
+ },
+ {
+ "StartTime": 19450,
+ "Position": 123
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner.osu
new file mode 100644
index 0000000000..0fbd4dbf42
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner.osu
@@ -0,0 +1,20 @@
+osu file format v14
+
+[General]
+Mode: 2
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+256,192,18500,12,0,19450,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index ce1aee5c34..5b69d836a3 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
}
+ private float hpDrainRate;
+
protected override void SimulateAutoplay(Beatmap beatmap)
{
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
+
foreach (var obj in beatmap.HitObjects)
{
switch (obj)
{
case JuiceStream stream:
- foreach (var _ in stream.NestedHitObjects.Cast())
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ foreach (var nestedObject in stream.NestedHitObjects)
+ switch (nestedObject)
+ {
+ case TinyDroplet _:
+ AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect });
+ break;
+ case Droplet _:
+ AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect });
+ break;
+ case Fruit _:
+ AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ break;
+ }
break;
case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast())
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
}
+ }
- base.SimulateAutoplay(beatmap);
+ private const double harshness = 0.01;
+
+ protected override void OnNewJudgement(Judgement judgement)
+ {
+ base.OnNewJudgement(judgement);
+
+ if (judgement.Result == HitResult.Miss)
+ {
+ if (!judgement.IsBonus)
+ Health.Value -= hpDrainRate * (harshness * 2);
+ return;
+ }
+
+ if (judgement is CatchJudgement catchJudgement)
+ Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 52763e09af..1ac052de4d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
switch (h)
{
+ case Banana banana:
+ return new DrawableBanana(banana);
case Fruit fruit:
return new DrawableFruit(fruit);
case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation);
- case BananaShower banana:
- return new DrawableBananaShower(banana, GetVisualRepresentation);
+ case BananaShower shower:
+ return new DrawableBananaShower(shower, GetVisualRepresentation);
case TinyDroplet tiny:
- return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) };
+ return new DrawableTinyDroplet(tiny);
case Droplet droplet:
return new DrawableDroplet(droplet);
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 30f4979255..9c376f340a 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
@@ -78,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
-
}
if (fruit.HitObject.LastInCombo)
{
- if (judgement.IsHit)
+ if (((CatchJudgement)judgement).ShouldExplode)
runAfterLoaded(() => MovableCatcher.Explode());
else
MovableCatcher.Drop();
@@ -250,51 +250,62 @@ namespace osu.Game.Rulesets.Catch.UI
if (validCatch && fruit.HyperDash)
{
- HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED;
- HyperDashDirection = fruit.HyperDashTarget.X - fruit.X;
+ var target = fruit.HyperDashTarget;
+ double timeDifference = target.StartTime - fruit.StartTime;
+ double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
+ double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
+
+ SetHyperdashState(Math.Abs(velocity), target.X);
}
else
- HyperDashModifier = 1;
+ {
+ SetHyperdashState();
+ }
return validCatch;
}
+ private double hyperDashModifier = 1;
+ private int hyperDashDirection;
+ private float hyperDashTargetPosition;
+
///
/// Whether we are hypderdashing or not.
///
public bool HyperDashing => hyperDashModifier != 1;
- private double hyperDashModifier = 1;
-
///
- /// The direction in which hyperdash is allowed. 0 allows both directions.
+ /// Set hyperdash state.
///
- public double HyperDashDirection;
-
- ///
- /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
- ///
- public double HyperDashModifier
+ /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyperdashing state.
+ /// When this catcher crosses this position, this catcher ends hyperdashing.
+ public void SetHyperdashState(double modifier = 1, float targetPosition = -1)
{
- get { return hyperDashModifier; }
- set
+ const float hyperdash_transition_length = 180;
+
+ bool previouslyHyperDashing = HyperDashing;
+ if (modifier <= 1 || X == targetPosition)
{
- if (value == hyperDashModifier) return;
- hyperDashModifier = value;
+ hyperDashModifier = 1;
+ hyperDashDirection = 0;
- const float transition_length = 180;
-
- if (HyperDashing)
+ if (previouslyHyperDashing)
{
- this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
- this.FadeTo(0.2f, transition_length, Easing.OutQuint);
- Trail = true;
+ this.FadeColour(Color4.White, hyperdash_transition_length, Easing.OutQuint);
+ this.FadeTo(1, hyperdash_transition_length, Easing.OutQuint);
}
- else
+ }
+ else
+ {
+ hyperDashModifier = modifier;
+ hyperDashDirection = Math.Sign(targetPosition - X);
+ hyperDashTargetPosition = targetPosition;
+
+ if (!previouslyHyperDashing)
{
- HyperDashDirection = 0;
- this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
- this.FadeTo(1, transition_length, Easing.OutQuint);
+ this.FadeColour(Color4.OrangeRed, hyperdash_transition_length, Easing.OutQuint);
+ this.FadeTo(0.2f, hyperdash_transition_length, Easing.OutQuint);
+ Trail = true;
}
}
}
@@ -349,12 +360,18 @@ namespace osu.Game.Rulesets.Catch.UI
var direction = Math.Sign(currentDirection);
double dashModifier = Dashing ? 1 : 0.5;
-
- if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
- dashModifier = hyperDashModifier;
+ double speed = BASE_SPEED * dashModifier * hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
- X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
+ X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
+
+ // Correct overshooting.
+ if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
+ hyperDashDirection < 0 && hyperDashTargetPosition > X)
+ {
+ X = hyperDashTargetPosition;
+ SetHyperdashState();
+ }
}
///
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 33ac1e00e4..58f2ab7747 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
+ [TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
new file mode 100644
index 0000000000..75c8fc7e79
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
@@ -0,0 +1,45 @@
+// 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 osu.Framework.Input.Bindings;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public abstract class ManiaInputTestCase : OsuTestCase
+ {
+ private readonly Container content;
+ protected override Container Content => content ?? base.Content;
+
+ protected ManiaInputTestCase(int keys)
+ {
+ base.Content.Add(content = new LocalInputManager(keys));
+ }
+
+ private class LocalInputManager : ManiaInputManager
+ {
+ public LocalInputManager(int variant)
+ : base(new ManiaRuleset().RulesetInfo, variant)
+ {
+ }
+
+ protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new LocalKeyBindingContainer(ruleset, variant, unique);
+
+ private class LocalKeyBindingContainer : RulesetKeyBindingContainer
+ {
+ public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+
+ protected override void ReloadMappings()
+ {
+ KeyBindings = DefaultKeyBindings;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
new file mode 100644
index 0000000000..78a98e83e8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.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 osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ ///
+ /// A container which provides a to children.
+ ///
+ public class ScrollingTestContainer : Container
+ {
+ private readonly ScrollingDirection direction;
+
+ public ScrollingTestContainer(ScrollingDirection direction)
+ {
+ this.direction = direction;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(new ScrollingInfo { Direction = { Value = direction }});
+ return dependencies;
+ }
+
+ private class ScrollingInfo : IScrollingInfo
+ {
+ public readonly Bindable Direction = new Bindable();
+ IBindable IScrollingInfo.Direction => Direction;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
new file mode 100644
index 0000000000..de2bfaed9c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
@@ -0,0 +1,111 @@
+// 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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Mania.UI.Components;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseColumn : ManiaInputTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Column),
+ typeof(ColumnBackground),
+ typeof(ColumnKeyArea),
+ typeof(ColumnHitObjectArea)
+ };
+
+ private readonly List columns = new List();
+
+ public TestCaseColumn()
+ : base(2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(20, 0),
+ Children = new[]
+ {
+ createColumn(ScrollingDirection.Up, ManiaAction.Key1),
+ createColumn(ScrollingDirection.Down, ManiaAction.Key2)
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("note", createNote);
+ AddStep("hold note", createHoldNote);
+ }
+
+ private void createNote()
+ {
+ for (int i = 0; i < columns.Count; i++)
+ {
+ var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ columns[i].Add(new DrawableNote(obj));
+ }
+ }
+
+ private void createHoldNote()
+ {
+ for (int i = 0; i < columns.Count; i++)
+ {
+ var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ columns[i].Add(new DrawableHoldNote(obj));
+ }
+ }
+
+ private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
+ {
+ var column = new Column(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Height = 0.85f,
+ AccentColour = Color4.OrangeRed,
+ Action = { Value = action },
+ VisibleTimeRange = { Value = 2000 }
+ };
+
+ columns.Add(column);
+
+ return new ScrollingTestContainer(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Child = column
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
deleted file mode 100644
index a4109722d4..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Tests.Visual;
-using OpenTK;
-using OpenTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestCaseManiaHitObjects : OsuTestCase
- {
- public TestCaseManiaHitObjects()
- {
- Note note1 = new Note();
- Note note2 = new Note();
- HoldNote holdNote = new HoldNote { StartTime = 1000 };
-
- note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- Add(new FillFlowContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- // Imagine that the containers containing the drawable notes are the "columns"
- Children = new Drawable[]
- {
- new Container
- {
- Name = "Normal note column",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Width = 50,
- Children = new[]
- {
- new Container
- {
- Name = "Timing section",
- RelativeSizeAxes = Axes.Both,
- RelativeChildSize = new Vector2(1, 10000),
- Children = new[]
- {
- new DrawableNote(note1, ManiaAction.Key1)
- {
- Y = 5000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red
- },
- new DrawableNote(note2, ManiaAction.Key1)
- {
- Y = 6000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red
- }
- }
- }
- }
- },
- new Container
- {
- Name = "Hold note column",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Width = 50,
- Children = new[]
- {
- new Container
- {
- Name = "Timing section",
- RelativeSizeAxes = Axes.Both,
- RelativeChildSize = new Vector2(1, 10000),
- Children = new[]
- {
- new DrawableHoldNote(holdNote, ManiaAction.Key1)
- {
- Y = 5000,
- Height = 1000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red,
- }
- }
- }
- }
- }
- }
- });
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
deleted file mode 100644
index b064d82a23..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-// 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.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Timing;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Configuration;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Configuration;
-using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Tests.Visual;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestCaseManiaPlayfield : OsuTestCase
- {
- private const double start_time = 500;
- private const double duration = 500;
-
- protected override double TimePerAction => 200;
-
- private RulesetInfo maniaRuleset;
-
- public TestCaseManiaPlayfield()
- {
- var rng = new Random(1337);
-
- AddStep("1 column", () => createPlayfield(1));
- AddStep("4 columns", () => createPlayfield(4));
- AddStep("5 columns", () => createPlayfield(5));
- AddStep("8 columns", () => createPlayfield(8));
- AddStep("4 + 4 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 4 },
- new StageDefinition { Columns = 4 },
- };
- createPlayfield(stages);
- });
-
- AddStep("2 + 4 + 2 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 2 },
- new StageDefinition { Columns = 4 },
- new StageDefinition { Columns = 2 },
- };
- createPlayfield(stages);
- });
-
- AddStep("1 + 8 + 1 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 1 },
- new StageDefinition { Columns = 8 },
- new StageDefinition { Columns = 1 },
- };
- createPlayfield(stages);
- });
-
- AddStep("Reversed", () => createPlayfield(4, true));
-
- AddStep("Notes with input", () => createPlayfieldWithNotes());
- AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
- AddStep("Notes with gravity", () => createPlayfieldWithNotes());
- AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
-
- AddStep("Hit explosion", () =>
- {
- var playfield = createPlayfield(4);
-
- int col = rng.Next(0, 4);
-
- var note = new Note { Column = col };
- note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- var drawableNote = new DrawableNote(note, ManiaAction.Key1)
- {
- AccentColour = playfield.Columns.ElementAt(col).AccentColour
- };
-
- playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
- playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
- });
- }
-
- [BackgroundDependencyLoader]
- private void load(RulesetStore rulesets, SettingsStore settings)
- {
- maniaRuleset = rulesets.GetRuleset(3);
-
- Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
- }
-
- private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
- {
- var stages = new List
- {
- new StageDefinition { Columns = cols },
- };
-
- return createPlayfield(stages, inverted);
- }
-
- private ManiaPlayfield createPlayfield(List stages, bool inverted = false)
- {
- Clear();
-
- var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
- Add(inputManager);
-
- ManiaPlayfield playfield;
-
- inputManager.Add(playfield = new ManiaPlayfield(stages)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- });
-
- playfield.Inverted.Value = inverted;
-
- return playfield;
- }
-
- private void createPlayfieldWithNotes(bool inverted = false)
- {
- Clear();
-
- var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
-
- var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
- Add(inputManager);
-
- ManiaPlayfield playfield;
- var stages = new List
- {
- new StageDefinition { Columns = 4 },
- };
-
- inputManager.Add(playfield = new ManiaPlayfield(stages)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Clock = new FramedClock(rateAdjustClock)
- });
-
- playfield.Inverted.Value = inverted;
-
- for (double t = start_time; t <= start_time + duration; t += 100)
- {
- var note1 = new Note { StartTime = t, Column = 0 };
- var note2 = new Note { StartTime = t, Column = 3 };
-
- note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
- playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
- }
-
- var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
- var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
-
- holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
- playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
new file mode 100644
index 0000000000..e827fa1fdc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
@@ -0,0 +1,173 @@
+// 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.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseNotes : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableNote),
+ typeof(DrawableHoldNote)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20),
+ Children = new[]
+ {
+ createNoteDisplay(ScrollingDirection.Down),
+ createNoteDisplay(ScrollingDirection.Up),
+ createHoldNoteDisplay(ScrollingDirection.Down),
+ createHoldNoteDisplay(ScrollingDirection.Up),
+ }
+ };
+ }
+
+ private Drawable createNoteDisplay(ScrollingDirection direction)
+ {
+ var note = new Note { StartTime = 999999999 };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new ScrollingTestContainer(direction)
+ {
+ AutoSizeAxes = Axes.Both,
+ Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
+ {
+ Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
+ }
+ };
+ }
+
+ private Drawable createHoldNoteDisplay(ScrollingDirection direction)
+ {
+ var note = new HoldNote { StartTime = 999999999, Duration = 1000 };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new ScrollingTestContainer(direction)
+ {
+ AutoSizeAxes = Axes.Both,
+ Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
+ {
+ Child = new DrawableHoldNote(note)
+ {
+ RelativeSizeAxes = Axes.Both,
+ AccentColour = Color4.OrangeRed,
+ }
+ }
+ };
+ }
+
+ private class NoteContainer : Container
+ {
+ private readonly Container content;
+ protected override Container Content => content;
+
+ private readonly ScrollingDirection direction;
+
+ public NoteContainer(ScrollingDirection direction, string description)
+ {
+ this.direction = direction;
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(0, 10),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Width = 45,
+ Height = 100,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Both,
+ Width = 1.25f,
+ Colour = Color4.Black.Opacity(0.5f)
+ },
+ content = new Container { RelativeSizeAxes = Axes.Both }
+ }
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ TextSize = 14,
+ Text = description
+ }
+ }
+ };
+ }
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs>(new Bindable());
+ return dependencies;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ foreach (var obj in content.OfType())
+ {
+ if (!(obj.HitObject is IHasEndTime endTime))
+ continue;
+
+ foreach (var nested in obj.NestedHitObjects)
+ {
+ double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
+ switch (direction)
+ {
+ case ScrollingDirection.Up:
+ nested.Y = (float)(finalPosition * content.DrawHeight);
+ break;
+ case ScrollingDirection.Down:
+ nested.Y = (float)(-finalPosition * content.DrawHeight);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
new file mode 100644
index 0000000000..8046c46fc1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
@@ -0,0 +1,121 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseStage : ManiaInputTestCase
+ {
+ private const int columns = 4;
+
+ private readonly List stages = new List();
+
+ public TestCaseStage()
+ : base(columns)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(20, 0),
+ Children = new[]
+ {
+ createStage(ScrollingDirection.Up, ManiaAction.Key1),
+ createStage(ScrollingDirection.Down, ManiaAction.Key3)
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("note", createNote);
+ AddStep("hold note", createHoldNote);
+ AddStep("minor bar line", () => createBarLine(false));
+ AddStep("major bar line", () => createBarLine(true));
+ }
+
+ private void createNote()
+ {
+ foreach (var stage in stages)
+ {
+ for (int i = 0; i < stage.Columns.Count; i++)
+ {
+ var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(new DrawableNote(obj));
+ }
+ }
+ }
+
+ private void createHoldNote()
+ {
+ foreach (var stage in stages)
+ {
+ for (int i = 0; i < stage.Columns.Count; i++)
+ {
+ var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(new DrawableHoldNote(obj));
+ }
+ }
+ }
+
+ private void createBarLine(bool major)
+ {
+ foreach (var stage in stages)
+ {
+ var obj = new BarLine
+ {
+ StartTime = Time.Current + 2000,
+ ControlPoint = new TimingControlPoint(),
+ BeatIndex = major ? 0 : 1
+ };
+
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(obj);
+ }
+ }
+
+ private Drawable createStage(ScrollingDirection direction, ManiaAction action)
+ {
+ var specialAction = ManiaAction.Special1;
+
+ var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
+ stages.Add(stage);
+
+ return new ScrollingTestContainer(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Child = stage
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
index d9e360081d..3e9c9feba1 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
@@ -4,6 +4,7 @@
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
+using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration
{
@@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
+ Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
@@ -29,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaSetting
{
- ScrollTime
+ ScrollTime,
+ ScrollDirection
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
new file mode 100644
index 0000000000..c7f6890b93
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+ public class ManiaDifficultyAttributes : DifficultyAttributes
+ {
+ public double GreatHitWindow;
+
+ public ManiaDifficultyAttributes(Mod[] mods, double starRating)
+ : base(mods, starRating)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 5fa113224d..7b4d4b12ed 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new ManiaDifficultyAttributes(mods, 0);
+
var difficultyHitObjects = new List();
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
@@ -50,9 +53,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
+
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
- return new DifficultyAttributes(mods, starRating);
+ return new ManiaDifficultyAttributes(mods, starRating)
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
+ };
}
private bool calculateStrainValues(List objects, double timeRate)
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index b6089b830b..1f26bda1b2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
+ protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
+
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
@@ -105,14 +107,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeAccuracyValue(double strainValue)
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- if (hitWindowGreat <= 0)
+ if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
+ double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
index 9630ba9273..9055e48a4c 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
public class HoldNoteJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
+
protected override int NumericResultFor(HitResult result) => 0;
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index ac5fbfdde0..3d58e63da5 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
+using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -155,6 +156,8 @@ namespace osu.Game.Rulesets.Mania
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
+ public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
+
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
new file mode 100644
index 0000000000..54a7bf954d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -0,0 +1,34 @@
+// 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.Game.Overlays.Settings;
+using osu.Game.Rulesets.Mania.Configuration;
+using osu.Game.Rulesets.Mania.UI;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class ManiaSettingsSubsection : RulesetSettingsSubsection
+ {
+ protected override string Header => "osu!mania";
+
+ public ManiaSettingsSubsection(ManiaRuleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ManiaConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = "Scrolling direction",
+ Bindable = config.GetBindable(ManiaSetting.ScrollDirection)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index e008fa952e..597450f223 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -37,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container tickContainer;
- public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
- : base(hitObject, action)
+ public DrawableHoldNote(HoldNote hitObject)
+ : base(hitObject)
{
RelativeSizeAxes = Axes.X;
@@ -56,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = () => holdStartTime
})
},
- head = new DrawableHeadNote(this, action)
+ head = new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
- tail = new DrawableTailNote(this, action)
+ tail = new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
@@ -75,6 +76,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(tail);
}
+ protected override void OnDirectionChanged(ScrollingDirection direction)
+ {
+ base.OnDirectionChanged(direction);
+
+ bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ }
+
public override Color4 AccentColour
{
get { return base.AccentColour; }
@@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.Update();
// Make the body piece not lie under the head note
- bodyPiece.Y = head.Height / 2;
+ bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * head.Height / 2;
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2;
}
@@ -110,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
@@ -127,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdStartTime.HasValue)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
holdStartTime = null;
@@ -146,8 +154,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly DrawableHoldNote holdNote;
- public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action)
- : base(holdNote.HitObject.Head, action)
+ public DrawableHeadNote(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Head)
{
this.holdNote = holdNote;
}
@@ -183,8 +191,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly DrawableHoldNote holdNote;
- public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
- : base(holdNote.HitObject.Tail, action)
+ public DrawableTailNote(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Tail)
{
this.holdNote = holdNote;
}
@@ -227,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdNote.holdStartTime.HasValue)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
UpdateJudgement(true);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index fbf1ee8460..cb6196a890 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -1,31 +1,55 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
- public abstract class DrawableManiaHitObject : DrawableHitObject
- where TObject : ManiaHitObject
+ public abstract class DrawableManiaHitObject : DrawableHitObject
{
///
- /// The key that will trigger input for this hit object.
+ /// The which causes this to be hit.
///
- protected ManiaAction Action { get; }
+ protected readonly IBindable Action = new Bindable();
- public new TObject HitObject;
+ protected readonly IBindable Direction = new Bindable();
- protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
+ protected DrawableManiaHitObject(ManiaHitObject hitObject)
: base(hitObject)
{
- Anchor = Anchor.TopCentre;
- Origin = Anchor.TopCentre;
-
- HitObject = hitObject;
+ }
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] IBindable action, [NotNull] IScrollingInfo scrollingInfo)
+ {
if (action != null)
- Action = action.Value;
+ Action.BindTo(action);
+
+ Direction.BindTo(scrollingInfo.Direction);
+ Direction.BindValueChanged(OnDirectionChanged, true);
+ }
+
+ protected virtual void OnDirectionChanged(ScrollingDirection direction)
+ {
+ Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ }
+ }
+
+ public abstract class DrawableManiaHitObject : DrawableManiaHitObject
+ where TObject : ManiaHitObject
+ {
+ public new readonly TObject HitObject;
+
+ protected DrawableManiaHitObject(TObject hitObject)
+ : base(hitObject)
+ {
+ HitObject = hitObject;
}
protected override void UpdateState(ArmedState state)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 3de0a9c5cc..18084c4c08 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -19,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly NotePiece headPiece;
- public DrawableNote(Note hitObject, ManiaAction action)
- : base(hitObject, action)
+ public DrawableNote(Note hitObject)
+ : base(hitObject)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@@ -28,14 +29,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
CornerRadius = 5;
Masking = true;
- InternalChildren = new Drawable[]
- {
- headPiece = new NotePiece
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre
- }
- };
+ InternalChild = headPiece = new NotePiece();
+ }
+
+ protected override void OnDirectionChanged(ScrollingDirection direction)
+ {
+ base.OnDirectionChanged(direction);
+
+ headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
public override Color4 AccentColour
@@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public virtual bool OnPressed(ManiaAction action)
{
- if (action != Action)
+ if (action != Action.Value)
return false;
return UpdateJudgement(true);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index 9ebeb91e0c..2c74f5b168 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -1,12 +1,16 @@
// 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.Configuration;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
@@ -18,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public const float NOTE_HEIGHT = 10;
private const float head_colour_height = 6;
+ private readonly IBindable direction = new Bindable();
+
private readonly Box colouredBox;
public NotePiece()
@@ -33,8 +39,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
},
colouredBox = new Box
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = head_colour_height,
Alpha = 0.2f
@@ -42,6 +46,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
};
}
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ }, true);
+ }
+
private Color4 accentColour;
public Color4 AccentColour
{
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9c1830e642..a19a6fb5d4 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -1,50 +1,39 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK;
using OpenTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
-using System;
using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
- public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
+ public class Column : ManiaScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
- private const float key_icon_size = 10;
- private const float key_icon_corner_radius = 3;
- private const float key_icon_border_radius = 2;
-
- private const float hit_target_height = 10;
- private const float hit_target_bar_height = 2;
-
private const float column_width = 45;
private const float special_column_width = 70;
- public ManiaAction Action;
+ public readonly Bindable Action = new Bindable();
- private readonly Box background;
- private readonly Box backgroundOverlay;
- private readonly Container hitTargetBar;
- private readonly Container keyIcon;
+ private readonly ColumnBackground background;
+ private readonly ColumnKeyArea keyArea;
+ private readonly ColumnHitObjectArea hitObjectArea;
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
- protected override Container Content => content;
- private readonly Container content;
+ protected override Container Content => hitObjectArea;
- public Column()
- : base(ScrollingDirection.Up)
+ public Column(ScrollingDirection direction)
+ : base(direction)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;
@@ -52,71 +41,21 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true;
CornerRadius = 5;
- InternalChildren = new Drawable[]
+ background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
+
+ Container hitTargetContainer;
+
+ InternalChildren = new[]
{
- background = new Box
- {
- Name = "Background",
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.3f
- },
- backgroundOverlay = new Box
- {
- Name = "Background Gradient Overlay",
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.TopLeft,
- Blending = BlendingMode.Additive,
- Alpha = 0
- },
- new Container
+ // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
+ background.CreateProxy(),
+ hitTargetContainer = new Container
{
Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION },
Children = new Drawable[]
{
- new Container
- {
- Name = "Hit target",
- RelativeSizeAxes = Axes.X,
- Height = hit_target_height,
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Background",
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black
- },
- hitTargetBar = new Container
- {
- Name = "Bar",
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both
- }
- }
- }
- }
- },
- content = new Container
- {
- Name = "Hit objects",
- RelativeSizeAxes = Axes.Both,
- },
- // For column lighting, we need to capture input events before the notes
- new InputTarget
- {
- Pressed = onPressed,
- Released = onReleased
- },
+ hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
explosionContainer = new Container
{
Name = "Hit explosions",
@@ -124,46 +63,27 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
},
- new Container
+ keyArea = new ColumnKeyArea
{
- Name = "Key",
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Key gradient",
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
- Alpha = 0.5f
- },
- keyIcon = new Container
- {
- Name = "Key icon",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(key_icon_size),
- Masking = true,
- CornerRadius = key_icon_corner_radius,
- BorderThickness = 2,
- BorderColour = Color4.White, // Not true
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- }
- }
- }
- }
},
+ background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
TopLevelContainer.Add(explosionContainer.CreateProxy());
+
+ Direction.BindValueChanged(d =>
+ {
+ hitTargetContainer.Padding = new MarginPadding
+ {
+ Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
+ Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
+ };
+
+ keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ }, true);
}
public override Axes RelativeSizeAxes => Axes.Y;
@@ -192,25 +112,19 @@ namespace osu.Game.Rulesets.Mania.UI
return;
accentColour = value;
- background.Colour = accentColour;
- backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0));
-
- hitTargetBar.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
-
- keyIcon.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
+ background.AccentColour = value;
+ keyArea.AccentColour = value;
+ hitObjectArea.AccentColour = value;
}
}
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs>(Action);
+ return dependencies;
+ }
+
///
/// Adds a DrawableHitObject to this Playfield.
///
@@ -228,48 +142,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return;
- explosionContainer.Add(new HitExplosion(judgedObject));
- }
-
- private bool onPressed(ManiaAction action)
- {
- if (action == Action)
+ explosionContainer.Add(new HitExplosion(judgedObject)
{
- backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
- keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
- }
-
- return false;
- }
-
- private bool onReleased(ManiaAction action)
- {
- if (action == Action)
- {
- backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
- keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
- }
-
- return false;
- }
-
- ///
- /// This is a simple container which delegates various input events that have to be captured before the notes.
- ///
- private class InputTarget : Container, IKeyBindingHandler
- {
- public Func Pressed;
- public Func Released;
-
- public InputTarget()
- {
- RelativeSizeAxes = Axes.Both;
- AlwaysPresent = true;
- Alpha = 0;
- }
-
- public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
- public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
+ Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
+ });
}
public bool OnPressed(ManiaAction action)
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
new file mode 100644
index 0000000000..19cc8fffef
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -0,0 +1,108 @@
+// 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.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
+ {
+ private readonly IBindable action = new Bindable();
+
+ private Box background;
+ private Box backgroundOverlay;
+
+ private readonly IBindable direction = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(IBindable action, IScrollingInfo scrollingInfo)
+ {
+ this.action.BindTo(action);
+
+ InternalChildren = new[]
+ {
+ background = new Box
+ {
+ Name = "Background",
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.3f
+ },
+ backgroundOverlay = new Box
+ {
+ Name = "Background Gradient Overlay",
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.5f,
+ Blending = BlendingMode.Additive,
+ Alpha = 0
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ updateColours();
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ background.Colour = AccentColour;
+
+ var brightPoint = AccentColour.Opacity(0.6f);
+ var dimPoint = AccentColour.Opacity(0);
+
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(
+ direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
+ direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == this.action.Value)
+ backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public bool OnReleased(ManiaAction action)
+ {
+ if (action == this.action.Value)
+ backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
new file mode 100644
index 0000000000..b5dfb0949a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -0,0 +1,99 @@
+// 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.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnHitObjectArea : Container, 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;
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ Drawable hitTargetBar;
+
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_height,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ content = new Container
+ {
+ Name = "Hit objects",
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+
+ hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
+ hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
new file mode 100644
index 0000000000..e30a033831
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
@@ -0,0 +1,123 @@
+// 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.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
+ {
+ private const float key_icon_size = 10;
+ private const float key_icon_corner_radius = 3;
+
+ private readonly IBindable action = new Bindable();
+ private readonly IBindable direction = new Bindable();
+
+ private Container keyIcon;
+
+ [BackgroundDependencyLoader]
+ private void load(IBindable action, IScrollingInfo scrollingInfo)
+ {
+ this.action.BindTo(action);
+
+ Drawable gradient;
+
+ InternalChildren = new[]
+ {
+ gradient = new Box
+ {
+ Name = "Key gradient",
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.5f
+ },
+ keyIcon = new Container
+ {
+ Name = "Key icon",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(key_icon_size),
+ Masking = true,
+ CornerRadius = key_icon_corner_radius,
+ BorderThickness = 2,
+ BorderColour = Color4.White, // Not true
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ gradient.Colour = ColourInfo.GradientVertical(
+ direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
+ direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ keyIcon.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == this.action.Value)
+ keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public bool OnReleased(ManiaAction action)
+ {
+ if (action == this.action.Value)
+ keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index bf8db63137..c74745868a 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
bool isTick = judgedObject is DrawableHoldNoteTick;
- Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs b/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
new file mode 100644
index 0000000000..ee65e9f1a5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Configuration;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public interface IScrollingInfo
+ {
+ ///
+ /// The direction s should scroll in.
+ ///
+ IBindable Direction { get; }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 4b936fc7f9..2f8ad7b17e 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
@@ -17,18 +16,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
- public class ManiaPlayfield : ScrollingPlayfield
+ public class ManiaPlayfield : ManiaScrollingPlayfield
{
- ///
- /// Whether this playfield should be inverted. This flips everything inside the playfield.
- ///
- public readonly Bindable Inverted = new Bindable(true);
-
public List Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List stages = new List();
- public ManiaPlayfield(List stageDefinitions)
- : base(ScrollingDirection.Up)
+ public ManiaPlayfield(ScrollingDirection direction, List stageDefinitions)
+ : base(direction)
{
if (stageDefinitions == null)
throw new ArgumentNullException(nameof(stageDefinitions));
@@ -36,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
- Inverted.Value = true;
-
GridContainer playfieldGrid;
InternalChild = playfieldGrid = new GridContainer
{
@@ -50,9 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
int firstColumnIndex = 0;
for (int i = 0; i < stageDefinitions.Count; i++)
{
- var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
+ var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
- newStage.Inverted.BindTo(Inverted);
playfieldGrid.Content[0][i] = newStage;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index a3145d6035..e6ebf43c67 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
@@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
+ private readonly Bindable configDirection = new Bindable();
+ private ScrollingInfo scrollingInfo;
+
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -65,12 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(ManiaConfigManager config)
{
BarLines.ForEach(Playfield.Add);
+
+ config.BindWith(ManiaSetting.ScrollDirection, configDirection);
+ configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
}
- protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(scrollingInfo = new ScrollingInfo());
+ return dependencies;
+ }
+
+ protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -84,21 +101,25 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h)
{
- ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
-
- var holdNote = h as HoldNote;
- if (holdNote != null)
- return new DrawableHoldNote(holdNote, action);
-
- var note = h as Note;
- if (note != null)
- return new DrawableNote(note, action);
-
- return null;
+ switch (h)
+ {
+ case HoldNote holdNote:
+ return new DrawableHoldNote(holdNote);
+ case Note note:
+ return new DrawableNote(note);
+ default:
+ return null;
+ }
}
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
+
+ private class ScrollingInfo : IScrollingInfo
+ {
+ public readonly Bindable Direction = new Bindable();
+ IBindable IScrollingInfo.Direction => Direction;
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
new file mode 100644
index 0000000000..0fc9c1920a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public enum ManiaScrollingDirection
+ {
+ Up = ScrollingDirection.Up,
+ Down = ScrollingDirection.Down
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
new file mode 100644
index 0000000000..f1ff0665cd
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
@@ -0,0 +1,26 @@
+// 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.Configuration;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public class ManiaScrollingPlayfield : ScrollingPlayfield
+ {
+ private readonly IBindable direction = new Bindable();
+
+ public ManiaScrollingPlayfield(ScrollingDirection direction)
+ : base(direction)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction => Direction.Value = direction, true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index cb93613c7d..4c7deb4567 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -24,20 +23,15 @@ namespace osu.Game.Rulesets.Mania.UI
///
/// A collection of s.
///
- internal class ManiaStage : ScrollingPlayfield
+ internal class ManiaStage : ManiaScrollingPlayfield
{
public const float HIT_TARGET_POSITION = 50;
- ///
- /// Whether this playfield should be inverted. This flips everything inside the playfield.
- ///
- public readonly Bindable Inverted = new Bindable(true);
-
public IReadOnlyList Columns => columnFlow.Children;
private readonly FillFlowContainer columnFlow;
- protected override Container Content => content;
- private readonly Container content;
+ protected override Container Content => barLineContainer;
+ private readonly Container barLineContainer;
public Container Judgements => judgements;
private readonly JudgementContainer judgements;
@@ -49,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex;
- public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
- : base(ScrollingDirection.Up)
+ public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
+ : base(direction)
{
this.firstColumnIndex = firstColumnIndex;
@@ -106,13 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both,
Masking = true,
- Child = content = new Container
+ Child = barLineContainer = new Container
{
Name = "Bar lines",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
- Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
}
},
judgements = new JudgementContainer
@@ -131,23 +124,23 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
var isSpecial = definition.IsSpecialColumn(i);
- var column = new Column
+ var column = new Column(direction)
{
IsSpecial = isSpecial,
- Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++
+ Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
};
AddColumn(column);
}
- Inverted.ValueChanged += invertedChanged;
- Inverted.TriggerChange();
- }
-
- private void invertedChanged(bool newValue)
- {
- Scale = new Vector2(1, newValue ? -1 : 1);
- Judgements.Scale = Scale;
+ Direction.BindValueChanged(d =>
+ {
+ barLineContainer.Padding = new MarginPadding
+ {
+ Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
+ Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
+ };
+ }, true);
}
public void AddColumn(Column c)
@@ -218,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
- content.Width = columnFlow.Width;
+ barLineContainer.Width = columnFlow.Width;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 1383256352..3fa039d946 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -8,17 +8,19 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
-using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
+ [TestFixture]
public class OsuBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase("basic")]
[TestCase("colinear-perfect-curve")]
+ [TestCase("slider-ticks")]
public new void Test(string name)
{
base.Test(name);
@@ -26,17 +28,23 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
- var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192);
- var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
-
- yield return new ConvertValue
+ switch (hitObject)
{
- StartTime = hitObject.StartTime,
- EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
- StartX = startPosition.X,
- StartY = startPosition.Y,
- EndX = endPosition.X,
- EndY = endPosition.Y
+ case Slider slider:
+ foreach (var nested in slider.NestedHitObjects)
+ yield return createConvertValue(nested);
+ break;
+ default:
+ yield return createConvertValue(hitObject);
+ break;
+ }
+
+ ConvertValue createConvertValue(HitObject obj) => new ConvertValue
+ {
+ StartTime = obj.StartTime,
+ EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
+ X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
+ Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
};
}
@@ -52,17 +60,13 @@ namespace osu.Game.Rulesets.Osu.Tests
public double StartTime;
public double EndTime;
- public float StartX;
- public float StartY;
- public float EndX;
- public float EndY;
+ public float X;
+ public float Y;
public bool Equals(ConvertValue other)
- => Precision.AlmostEquals(StartTime, other.StartTime)
+ => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
- && Precision.AlmostEquals(StartX, other.StartX)
- && Precision.AlmostEquals(StartY, other.StartY, conversion_lenience)
- && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
- && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
+ && Precision.AlmostEquals(X, other.X, conversion_lenience)
+ && Precision.AlmostEquals(Y, other.Y, conversion_lenience);
}
}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 80eb808f6e..405493cde4 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition;
var comboData = original as IHasCombo;
+ var legacyOffset = original as IHasLegacyLastTickOffset;
if (curveData != null)
{
@@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
- NewCombo = comboData?.NewCombo ?? false
+ NewCombo = comboData?.NewCombo ?? false,
+ LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
};
}
else if (endTimeData != null)
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index c7c9f4a01a..bbe2d67baa 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
}
- public override void PostProcess()
+ public override void PreProcess()
{
+ base.PreProcess();
applyStacking((Beatmap)Beatmap);
- base.PostProcess();
}
private void applyStacking(Beatmap beatmap)
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
- double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
+ double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object.
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
- double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
+ double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
/* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 50a259ae55..2b42eed0c5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public double AimStrain;
public double SpeedStrain;
+ public double ApproachRate;
+ public double OverallDifficulty;
+ public int MaxCombo;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 400afbc043..62fafd8196 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new OsuDifficultyAttributes(mods, 0);
+
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate);
Skill[] skills =
{
@@ -58,10 +61,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+ // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ 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();
+ // 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)
+ maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
+
return new OsuDifficultyAttributes(mods, starRating)
{
AimStrain = aimRating,
- SpeedStrain = speedRating
+ SpeedStrain = speedRating,
+ ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5,
+ OverallDifficulty = (80 - hitWindowGreat) / 6,
+ MaxCombo = maxCombo
};
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 3ab3cc879a..d4a60dd52f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -22,16 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private Mod[] mods;
- ///
- /// Approach rate adjusted by mods.
- ///
- private double realApproachRate;
-
- ///
- /// Overall difficulty adjusted by mods.
- ///
- private double realOverallDifficulty;
-
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@@ -63,13 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => !m.Ranked))
return 0;
- // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
-
- realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
- realOverallDifficulty = (80 - hitWindowGreat) / 6;
-
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -94,8 +77,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
- categoryRatings.Add("OD", realOverallDifficulty);
- categoryRatings.Add("AR", realApproachRate);
+ categoryRatings.Add("OD", Attributes.OverallDifficulty);
+ categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo);
}
@@ -120,22 +103,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
double approachRateFactor = 1.0f;
- if (realApproachRate > 10.33f)
- approachRateFactor += 0.45f * (realApproachRate - 10.33f);
- else if (realApproachRate < 8.0f)
+ if (Attributes.ApproachRate > 10.33f)
+ approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
+ else if (Attributes.ApproachRate < 8.0f)
{
// HD is worth more with lower ar!
if (mods.Any(h => h is OsuModHidden))
- approachRateFactor += 0.02f * (8.0f - realApproachRate);
+ approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
else
- approachRateFactor += 0.01f * (8.0f - realApproachRate);
+ approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
- aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
+ aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight))
{
@@ -146,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
+ aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
}
@@ -172,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
+ speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return speedValue;
}
@@ -194,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+ double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
index b7c4470592..26becfdec9 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.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 OpenTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public override HitResult MaxResult => HitResult.Great;
- ///
- /// The positional hit offset.
- ///
- public Vector2 PositionOffset;
-
protected override int NumericResultFor(HitResult result)
{
switch (result)
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
index c4e265aac9..d52de9f971 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuSliderTailJudgement : OsuJudgement
{
public override bool AffectsCombo => false;
+
protected override int NumericResultFor(HitResult result) => 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 9fe6dcd46c..421c93d485 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement
{
Result = result,
- PositionOffset = Vector2.Zero //todo: set to correct value
});
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index dfab123038..f1907a92a8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value;
Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour;
+
+ foreach (var drawableHitObject in NestedHitObjects)
+ drawableHitObject.AccentColour = AccentColour;
}
}
@@ -133,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
if (!userTriggered && Time.Current >= slider.EndTime)
{
- var judgementsCount = NestedHitObjects.Count;
+ var judgementsCount = NestedHitObjects.Count();
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount;
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 54126b934f..befbc01f3c 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual bool NewCombo { get; set; }
- public int IndexInCurrentCombo { get; set; }
+ public virtual int IndexInCurrentCombo { get; set; }
- public int ComboIndex { get; set; }
+ public virtual int ComboIndex { get; set; }
public bool LastInCombo { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 7f4407370f..698f9de787 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.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 OpenTK;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -25,6 +26,28 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
+ public override int ComboIndex
+ {
+ get => base.ComboIndex;
+ set
+ {
+ base.ComboIndex = value;
+ foreach (var n in NestedHitObjects.OfType())
+ n.ComboIndex = value;
+ }
+ }
+
+ public override int IndexInCurrentCombo
+ {
+ get => base.IndexInCurrentCombo;
+ set
+ {
+ base.IndexInCurrentCombo = value;
+ foreach (var n in NestedHitObjects.OfType())
+ n.IndexInCurrentCombo = value;
+ }
+ }
+
public SliderCurve Curve { get; } = new SliderCurve();
public List ControlPoints
@@ -45,6 +68,8 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; }
}
+ public double? LegacyLastTickOffset { get; set; }
+
///
/// The position of the cursor at the point of completion of this if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation.
@@ -91,6 +116,9 @@ namespace osu.Game.Rulesets.Osu.Objects
createSliderEnds();
createTicks();
createRepeatPoints();
+
+ if (LegacyLastTickOffset != null)
+ TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value);
}
private void createSliderEnds()
@@ -141,7 +169,8 @@ namespace osu.Game.Rulesets.Osu.Objects
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
- var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
+ var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
+ ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List();
if (firstSample != null)
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
index 324d0502c1..bb0bd891b3 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
///
/// Constants (for spinners).
///
- protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192);
+ protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50;
///
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
index b82fddbe79..491adc2b50 100644
--- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -1,124 +1,256 @@
{
"Mappings": [{
- "StartTime": 500,
- "Objects": [{
- "StartTime": 500,
- "EndTime": 2500,
- "StartX": 96,
- "StartY": 192,
- "EndX": 96,
- "EndY": 192
- }]
- },
- {
- "StartTime": 3000,
- "Objects": [{
- "StartTime": 3000,
- "EndTime": 4000,
- "StartX": 256,
- "StartY": 192,
- "EndX": 256,
- "EndY": 192
- }]
- },
- {
- "StartTime": 4500,
- "Objects": [{
- "StartTime": 4500,
- "EndTime": 5500,
- "StartX": 256,
- "StartY": 192,
- "EndX": 256,
- "EndY": 192
- }]
- },
- {
- "StartTime": 6000,
- "Objects": [{
- "StartTime": 6000,
- "EndTime": 6500,
- "StartX": 256,
- "StartY": 192,
- "EndX": 256,
- "EndY": 192
- }]
- },
- {
- "StartTime": 7000,
- "Objects": [{
- "StartTime": 7000,
- "EndTime": 8000,
- "StartX": 256,
- "StartY": 128,
- "EndX": 256,
- "EndY": 128
- }]
- },
- {
- "StartTime": 8500,
- "Objects": [{
- "StartTime": 8500,
- "EndTime": 10999,
- "StartX": 32,
- "StartY": 192,
- "EndX": 508.166229,
- "EndY": 153.299271
- }]
- },
- {
- "StartTime": 11500,
- "Objects": [{
- "StartTime": 11500,
- "EndTime": 12000,
- "StartX": 256,
- "StartY": 192,
- "EndX": 256,
- "EndY": 192
- }]
- },
- {
- "StartTime": 12500,
- "Objects": [{
- "StartTime": 12500,
- "EndTime": 16500,
- "StartX": 512,
- "StartY": 320,
- "EndX": 291.1977,
- "EndY": 40.799427
- }]
- },
- {
- "StartTime": 17000,
- "Objects": [{
- "StartTime": 17000,
- "EndTime": 18000,
- "StartX": 256,
- "StartY": 256,
- "EndX": 256,
- "EndY": 256
- }]
- },
- {
- "StartTime": 18500,
- "Objects": [{
- "StartTime": 18500,
- "EndTime": 19450,
- "StartX": 256,
- "StartY": 192,
- "EndX": 256,
- "EndY": 192
- }]
- },
- {
- "StartTime": 19875,
- "Objects": [{
- "StartTime": 19875,
- "EndTime": 23874,
- "StartX": 216,
- "StartY": 231,
- "EndX": 408.720825,
- "EndY": 339.810455
- }]
- }
- ]
+ "StartTime": 500.0,
+ "Objects": [{
+ "StartTime": 500.0,
+ "EndTime": 500.0,
+ "X": 96.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 1000.0,
+ "EndTime": 1000.0,
+ "X": 256.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 1500.0,
+ "EndTime": 1500.0,
+ "X": 416.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 2000.0,
+ "EndTime": 2000.0,
+ "X": 256.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 2464.0,
+ "EndTime": 2464.0,
+ "X": 96.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 3000.0,
+ "Objects": [{
+ "StartTime": 3000.0,
+ "EndTime": 4000.0,
+ "X": 256.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 4500.0,
+ "Objects": [{
+ "StartTime": 4500.0,
+ "EndTime": 5500.0,
+ "X": 256.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 6000.0,
+ "Objects": [{
+ "StartTime": 6000.0,
+ "EndTime": 6500.0,
+ "X": 256.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 7000.0,
+ "Objects": [{
+ "StartTime": 7000.0,
+ "EndTime": 7000.0,
+ "X": 256.0,
+ "Y": 128.0
+ }, {
+ "StartTime": 7250.0,
+ "EndTime": 7250.0,
+ "X": 336.0,
+ "Y": 128.0
+ }, {
+ "StartTime": 7500.0,
+ "EndTime": 7500.0,
+ "X": 256.0,
+ "Y": 128.0
+ }, {
+ "StartTime": 7750.0,
+ "EndTime": 7750.0,
+ "X": 336.0,
+ "Y": 128.0
+ }, {
+ "StartTime": 7964.0,
+ "EndTime": 7964.0,
+ "X": 256.0,
+ "Y": 128.0
+ }]
+ }, {
+ "StartTime": 8500.0,
+ "Objects": [{
+ "StartTime": 8500.0,
+ "EndTime": 8500.0,
+ "X": 32.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 9000.0,
+ "EndTime": 9000.0,
+ "X": 101.81015,
+ "Y": 326.4915
+ }, {
+ "StartTime": 9500.0,
+ "EndTime": 9500.0,
+ "X": 237.2304,
+ "Y": 276.282928
+ }, {
+ "StartTime": 10000.0,
+ "EndTime": 10000.0,
+ "X": 270.339874,
+ "Y": 121.1423
+ }, {
+ "StartTime": 10500.0,
+ "EndTime": 10500.0,
+ "X": 401.0588,
+ "Y": 49.1515045
+ }, {
+ "StartTime": 10964.0,
+ "EndTime": 10964.0,
+ "X": 508.166229,
+ "Y": 153.299271
+ }]
+ }, {
+ "StartTime": 11500.0,
+ "Objects": [{
+ "StartTime": 11500.0,
+ "EndTime": 12000.0,
+ "X": 256.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 12500.0,
+ "Objects": [{
+ "StartTime": 12500.0,
+ "EndTime": 12500.0,
+ "X": 512.0,
+ "Y": 320.0
+ }, {
+ "StartTime": 13000.0,
+ "EndTime": 13000.0,
+ "X": 353.235535,
+ "Y": 300.154449
+ }, {
+ "StartTime": 13500.0,
+ "EndTime": 13500.0,
+ "X": 194.471069,
+ "Y": 280.3089
+ }, {
+ "StartTime": 14000.0,
+ "EndTime": 14000.0,
+ "X": 35.7066345,
+ "Y": 260.463318
+ }, {
+ "StartTime": 14500.0,
+ "EndTime": 14500.0,
+ "X": 118.370323,
+ "Y": 219.009277
+ }, {
+ "StartTime": 15000.0,
+ "EndTime": 15000.0,
+ "X": 271.087128,
+ "Y": 171.285278
+ }, {
+ "StartTime": 15500.0,
+ "EndTime": 15500.0,
+ "X": 423.803925,
+ "Y": 123.561279
+ }, {
+ "StartTime": 16000.0,
+ "EndTime": 16000.0,
+ "X": 446.420532,
+ "Y": 79.60513
+ }, {
+ "StartTime": 16464.0,
+ "EndTime": 16464.0,
+ "X": 291.1977,
+ "Y": 40.799427
+ }]
+ }, {
+ "StartTime": 17000.0,
+ "Objects": [{
+ "StartTime": 17000.0,
+ "EndTime": 17000.0,
+ "X": 256.0,
+ "Y": 256.0
+ }, {
+ "StartTime": 17250.0,
+ "EndTime": 17250.0,
+ "X": 176.0,
+ "Y": 256.0
+ }, {
+ "StartTime": 17500.0,
+ "EndTime": 17500.0,
+ "X": 256.0,
+ "Y": 256.0
+ }, {
+ "StartTime": 17750.0,
+ "EndTime": 17750.0,
+ "X": 176.0,
+ "Y": 256.0
+ }, {
+ "StartTime": 17964.0,
+ "EndTime": 17964.0,
+ "X": 256.0,
+ "Y": 256.0
+ }]
+ }, {
+ "StartTime": 18500.0,
+ "Objects": [{
+ "StartTime": 18500.0,
+ "EndTime": 19450.0,
+ "X": 256.0,
+ "Y": 192.0
+ }]
+ }, {
+ "StartTime": 19875.0,
+ "Objects": [{
+ "StartTime": 19875.0,
+ "EndTime": 19875.0,
+ "X": 216.0,
+ "Y": 231.0
+ }, {
+ "StartTime": 20375.0,
+ "EndTime": 20375.0,
+ "X": 317.446747,
+ "Y": 171.345245
+ }, {
+ "StartTime": 20875.0,
+ "EndTime": 20875.0,
+ "X": 270.3294,
+ "Y": 310.4395
+ }, {
+ "StartTime": 21375.0,
+ "EndTime": 21375.0,
+ "X": 119.121056,
+ "Y": 322.8657
+ }, {
+ "StartTime": 21875.0,
+ "EndTime": 21875.0,
+ "X": 124.28746,
+ "Y": 165.224731
+ }, {
+ "StartTime": 22375.0,
+ "EndTime": 22375.0,
+ "X": 240.4715,
+ "Y": 62.65587
+ }, {
+ "StartTime": 22875.0,
+ "EndTime": 22875.0,
+ "X": 398.054047,
+ "Y": 39.064167
+ }, {
+ "StartTime": 23375.0,
+ "EndTime": 23375.0,
+ "X": 439.749878,
+ "Y": 183.668091
+ }, {
+ "StartTime": 23839.0,
+ "EndTime": 23839.0,
+ "X": 408.720825,
+ "Y": 339.810455
+ }]
+ }]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
index 7fe038658c..96e4bf1637 100644
--- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
@@ -1,13 +1,16 @@
{
- "Mappings": [{
- "StartTime": 118858,
- "Objects": [{
- "StartTime": 118858,
- "EndTime": 119088,
- "StartX": 219,
- "StartY": 215,
- "EndX": 239.6507,
- "EndY": 29.1437378
+ "Mappings": [{
+ "StartTime": 118858.0,
+ "Objects": [{
+ "StartTime": 118858.0,
+ "EndTime": 118858.0,
+ "X": 219.0,
+ "Y": 215.0
+ }, {
+ "StartTime": 119052.0,
+ "EndTime": 119052.0,
+ "X": 239.6507,
+ "Y": 29.1437378
+ }]
}]
- }]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json
new file mode 100644
index 0000000000..9c049c7cd6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json
@@ -0,0 +1,331 @@
+{
+ "Mappings": [{
+ "StartTime": 500.0,
+ "Objects": [{
+ "StartTime": 500.0,
+ "EndTime": 500.0,
+ "X": 96.0,
+ "Y": 192.0
+ }, {
+ "StartTime": 624.0,
+ "EndTime": 624.0,
+ "X": 105.921242,
+ "Y": 192.0
+ }, {
+ "StartTime": 749.0,
+ "EndTime": 749.0,
+ "X": 115.922493,
+ "Y": 192.0
+ }, {
+ "StartTime": 874.0,
+ "EndTime": 874.0,
+ "X": 125.923737,
+ "Y": 192.0
+ }, {
+ "StartTime": 999.0,
+ "EndTime": 999.0,
+ "X": 135.924988,
+ "Y": 192.0
+ }, {
+ "StartTime": 1124.0,
+ "EndTime": 1124.0,
+ "X": 145.926239,
+ "Y": 192.0
+ }, {
+ "StartTime": 1249.0,
+ "EndTime": 1249.0,
+ "X": 155.92749,
+ "Y": 192.0
+ }, {
+ "StartTime": 1374.0,
+ "EndTime": 1374.0,
+ "X": 165.928741,
+ "Y": 192.0
+ }, {
+ "StartTime": 1499.0,
+ "EndTime": 1499.0,
+ "X": 175.93,
+ "Y": 192.0
+ }, {
+ "StartTime": 1624.0,
+ "EndTime": 1624.0,
+ "X": 185.931244,
+ "Y": 192.0
+ }, {
+ "StartTime": 1749.0,
+ "EndTime": 1749.0,
+ "X": 195.9325,
+ "Y": 192.0
+ }, {
+ "StartTime": 1874.0,
+ "EndTime": 1874.0,
+ "X": 205.933746,
+ "Y": 192.0
+ }, {
+ "StartTime": 1999.0,
+ "EndTime": 1999.0,
+ "X": 215.935,
+ "Y": 192.0
+ }, {
+ "StartTime": 2124.0,
+ "EndTime": 2124.0,
+ "X": 225.936234,
+ "Y": 192.0
+ }, {
+ "StartTime": 2249.0,
+ "EndTime": 2249.0,
+ "X": 235.9375,
+ "Y": 192.0
+ }, {
+ "StartTime": 2374.0,
+ "EndTime": 2374.0,
+ "X": 245.938751,
+ "Y": 192.0
+ }, {
+ "StartTime": 2499.0,
+ "EndTime": 2499.0,
+ "X": 255.94,
+ "Y": 192.0
+ }, {
+ "StartTime": 2624.0,
+ "EndTime": 2624.0,
+ "X": 265.941223,
+ "Y": 192.0
+ }, {
+ "StartTime": 2749.0,
+ "EndTime": 2749.0,
+ "X": 275.9425,
+ "Y": 192.0
+ }, {
+ "StartTime": 2874.0,
+ "EndTime": 2874.0,
+ "X": 285.943756,
+ "Y": 192.0
+ }, {
+ "StartTime": 2999.0,
+ "EndTime": 2999.0,
+ "X": 295.945,
+ "Y": 192.0
+ }, {
+ "StartTime": 3124.0,
+ "EndTime": 3124.0,
+ "X": 305.946259,
+ "Y": 192.0
+ }, {
+ "StartTime": 3249.0,
+ "EndTime": 3249.0,
+ "X": 315.9475,
+ "Y": 192.0
+ }, {
+ "StartTime": 3374.0,
+ "EndTime": 3374.0,
+ "X": 325.94873,
+ "Y": 192.0
+ }, {
+ "StartTime": 3499.0,
+ "EndTime": 3499.0,
+ "X": 335.949982,
+ "Y": 192.0
+ }, {
+ "StartTime": 3624.0,
+ "EndTime": 3624.0,
+ "X": 345.951233,
+ "Y": 192.0
+ }, {
+ "StartTime": 3749.0,
+ "EndTime": 3749.0,
+ "X": 355.952484,
+ "Y": 192.0
+ }, {
+ "StartTime": 3874.0,
+ "EndTime": 3874.0,
+ "X": 365.953766,
+ "Y": 192.0
+ }, {
+ "StartTime": 3999.0,
+ "EndTime": 3999.0,
+ "X": 375.955,
+ "Y": 192.0
+ }, {
+ "StartTime": 4124.0,
+ "EndTime": 4124.0,
+ "X": 385.956238,
+ "Y": 192.0
+ }, {
+ "StartTime": 4249.0,
+ "EndTime": 4249.0,
+ "X": 395.9575,
+ "Y": 192.0
+ }, {
+ "StartTime": 4374.0,
+ "EndTime": 4374.0,
+ "X": 405.95874,
+ "Y": 192.0
+ }, {
+ "StartTime": 4499.0,
+ "EndTime": 4499.0,
+ "X": 415.960022,
+ "Y": 192.0
+ }, {
+ "StartTime": 4624.0,
+ "EndTime": 4624.0,
+ "X": 406.038757,
+ "Y": 192.0
+ }, {
+ "StartTime": 4749.0,
+ "EndTime": 4749.0,
+ "X": 396.0375,
+ "Y": 192.0
+ }, {
+ "StartTime": 4874.0,
+ "EndTime": 4874.0,
+ "X": 386.036255,
+ "Y": 192.0
+ }, {
+ "StartTime": 4999.0,
+ "EndTime": 4999.0,
+ "X": 376.035034,
+ "Y": 192.0
+ }, {
+ "StartTime": 5124.0,
+ "EndTime": 5124.0,
+ "X": 366.033752,
+ "Y": 192.0
+ }, {
+ "StartTime": 5249.0,
+ "EndTime": 5249.0,
+ "X": 356.0325,
+ "Y": 192.0
+ }, {
+ "StartTime": 5374.0,
+ "EndTime": 5374.0,
+ "X": 346.03125,
+ "Y": 192.0
+ }, {
+ "StartTime": 5499.0,
+ "EndTime": 5499.0,
+ "X": 336.030029,
+ "Y": 192.0
+ }, {
+ "StartTime": 5624.0,
+ "EndTime": 5624.0,
+ "X": 326.028748,
+ "Y": 192.0
+ }, {
+ "StartTime": 5749.0,
+ "EndTime": 5749.0,
+ "X": 316.0275,
+ "Y": 192.0
+ }, {
+ "StartTime": 5874.0,
+ "EndTime": 5874.0,
+ "X": 306.026245,
+ "Y": 192.0
+ }, {
+ "StartTime": 5999.0,
+ "EndTime": 5999.0,
+ "X": 296.025,
+ "Y": 192.0
+ }, {
+ "StartTime": 6124.0,
+ "EndTime": 6124.0,
+ "X": 286.023773,
+ "Y": 192.0
+ }, {
+ "StartTime": 6249.0,
+ "EndTime": 6249.0,
+ "X": 276.022522,
+ "Y": 192.0
+ }, {
+ "StartTime": 6374.0,
+ "EndTime": 6374.0,
+ "X": 266.02124,
+ "Y": 192.0
+ }, {
+ "StartTime": 6499.0,
+ "EndTime": 6499.0,
+ "X": 256.02,
+ "Y": 192.0
+ }, {
+ "StartTime": 6624.0,
+ "EndTime": 6624.0,
+ "X": 246.018768,
+ "Y": 192.0
+ }, {
+ "StartTime": 6749.0,
+ "EndTime": 6749.0,
+ "X": 236.017517,
+ "Y": 192.0
+ }, {
+ "StartTime": 6874.0,
+ "EndTime": 6874.0,
+ "X": 226.016251,
+ "Y": 192.0
+ }, {
+ "StartTime": 6999.0,
+ "EndTime": 6999.0,
+ "X": 216.014984,
+ "Y": 192.0
+ }, {
+ "StartTime": 7124.0,
+ "EndTime": 7124.0,
+ "X": 206.013733,
+ "Y": 192.0
+ }, {
+ "StartTime": 7249.0,
+ "EndTime": 7249.0,
+ "X": 196.012512,
+ "Y": 192.0
+ }, {
+ "StartTime": 7374.0,
+ "EndTime": 7374.0,
+ "X": 186.011261,
+ "Y": 192.0
+ }, {
+ "StartTime": 7499.0,
+ "EndTime": 7499.0,
+ "X": 176.01,
+ "Y": 192.0
+ }, {
+ "StartTime": 7624.0,
+ "EndTime": 7624.0,
+ "X": 166.008728,
+ "Y": 192.0
+ }, {
+ "StartTime": 7749.0,
+ "EndTime": 7749.0,
+ "X": 156.0075,
+ "Y": 192.0
+ }, {
+ "StartTime": 7874.0,
+ "EndTime": 7874.0,
+ "X": 146.006256,
+ "Y": 192.0
+ }, {
+ "StartTime": 7999.0,
+ "EndTime": 7999.0,
+ "X": 136.005,
+ "Y": 192.0
+ }, {
+ "StartTime": 8124.0,
+ "EndTime": 8124.0,
+ "X": 126.003738,
+ "Y": 192.0
+ }, {
+ "StartTime": 8249.0,
+ "EndTime": 8249.0,
+ "X": 116.002518,
+ "Y": 192.0
+ }, {
+ "StartTime": 8374.0,
+ "EndTime": 8374.0,
+ "X": 106.001259,
+ "Y": 192.0
+ }, {
+ "StartTime": 8463.0,
+ "EndTime": 8463.0,
+ "X": 96.0,
+ "Y": 192.0
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks.osu
new file mode 100644
index 0000000000..fd835efbc8
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks.osu
@@ -0,0 +1,20 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.7
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:0.400000005960464
+SliderTickRate:4
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320.000004768372
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index f2d5631e93..04724931ae 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI;
using System.Linq;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{
Origin = Anchor.Centre,
- Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset
+ Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
};
judgementLayer.Add(explosion);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index fb0b2040f4..db8480c742 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
+ [TestFixture]
public class TaikoBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
new file mode 100644
index 0000000000..d18d03b1db
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty
+{
+ public class TaikoDifficultyAttributes : DifficultyAttributes
+ {
+ public double GreatHitWindow;
+ public int MaxCombo;
+
+ public TaikoDifficultyAttributes(Mod[] mods, double starRating)
+ : base(mods, starRating)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 473c205293..8b527c2c82 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -34,6 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new TaikoDifficultyAttributes(mods, 0);
+
var difficultyHitObjects = new List();
foreach (var hitObject in beatmap.HitObjects)
@@ -47,7 +51,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
- return new DifficultyAttributes(mods, starRating);
+ return new TaikoDifficultyAttributes(mods, starRating)
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
+ MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
+ };
}
private bool calculateStrainValues(List objects, double timeRate)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 53cfb4fd0f..f530b6725c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -8,13 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoPerformanceCalculator : PerformanceCalculator
{
- private readonly int beatmapMaxCombo;
+ protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
private Mod[] mods;
private int countGreat;
@@ -25,7 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
- beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
}
public override double Calculate(Dictionary categoryDifficulty = null)
@@ -78,8 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
strainValue *= Math.Pow(0.985, countMiss);
// Combo scaling
- if (beatmapMaxCombo > 0)
- strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
+ if (Attributes.MaxCombo > 0)
+ strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
if (mods.Any(m => m is ModHidden))
strainValue *= 1.025;
@@ -94,14 +92,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeAccuracyValue()
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- if (hitWindowGreat <= 0)
+ if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
+ double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
new file mode 100644
index 0000000000..608f1f9be2
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Judgements
+{
+ public class TaikoIntermediateSwellJudgement : TaikoJudgement
+ {
+ public override HitResult MaxResult => HitResult.Perfect;
+
+ public override bool AffectsCombo => false;
+
+ public TaikoIntermediateSwellJudgement()
+ {
+ Final = false;
+ }
+
+ ///
+ /// Computes the numeric result value for the combo portion of the score.
+ ///
+ /// The result to compute the value for.
+ /// The numeric result value.
+ protected override int NumericResultFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 519b56a3ed..bb9cd02b14 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (State.Value)
{
case ArmedState.Idle:
+ SecondHitAllowed = false;
+ validKeyPressed = false;
+
UnproxyContent();
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
break;
@@ -95,8 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
break;
case ArmedState.Hit:
// If we're far enough away from the left stage, we should bring outselves in front of it
- if (X >= -0.05f)
- ProxyContent();
+ ProxyContent();
var flash = circlePiece?.FlashBox;
if (flash != null)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
index c416d50062..b431d35e16 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
@@ -46,6 +47,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great });
}
+ protected override void UpdateState(ArmedState state)
+ {
+ base.UpdateState(state);
+
+ switch (state)
+ {
+ case ArmedState.Idle:
+ firstHitTime = 0;
+ firstKeyHeld = false;
+ break;
+ }
+ }
+
public override bool OnReleased(TaikoAction action)
{
if (action == firstHitAction)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index df36a475d6..408b37e377 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -20,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableSwell : DrawableTaikoHitObject
{
+ ///
+ /// A judgement is only displayed when the user has complete the swell (either a hit or miss).
+ ///
+ public override bool DisplayJudgement => AllJudged;
+
private const float target_ring_thick_border = 1.4f;
private const float target_ring_thin_border = 1f;
private const float target_ring_scale = 5f;
@@ -29,11 +33,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
- ///
- /// The amount of times the user has hit this swell.
- ///
- private int userHits;
-
private readonly SwellSymbolPiece symbol;
public DrawableSwell(Swell swell)
@@ -129,9 +128,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
if (userTriggered)
{
- userHits++;
+ AddJudgement(new TaikoIntermediateSwellJudgement());
- var completion = (float)userHits / HitObject.RequiredHits;
+ var completion = (float)Judgements.Count / HitObject.RequiredHits;
expandingRing
.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
@@ -142,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
- if (userHits == HitObject.RequiredHits)
+ if (Judgements.Count == HitObject.RequiredHits)
AddJudgement(new TaikoJudgement { Result = HitResult.Great });
}
else
@@ -151,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return;
//TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP
- AddJudgement(userHits > HitObject.RequiredHits / 2
+ AddJudgement(Judgements.Count > HitObject.RequiredHits / 2
? new TaikoJudgement { Result = HitResult.Good }
: new TaikoJudgement { Result = HitResult.Miss });
}
@@ -162,23 +161,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
const float preempt = 100;
const float out_transition_time = 300;
- double untilStartTime = HitObject.StartTime - Time.Current;
- double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration;
-
- targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
- this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out);
-
switch (state)
{
case ArmedState.Idle:
UnproxyContent();
+ expandingRing.FadeTo(0);
+ using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
+ targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
break;
+ case ArmedState.Miss:
case ArmedState.Hit:
- bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
+ this.FadeOut(out_transition_time, Easing.Out);
+ bodyContainer.ScaleTo(1.4f, out_transition_time);
+
+ Expire();
break;
}
-
- Expire();
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 313c205981..e04b4d45f6 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private void loadBarLines()
{
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
- double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
+ double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
@@ -69,11 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
- double bl = currentPoint.BeatLength;
- if (bl < 800)
- bl *= (int)currentPoint.TimeSignature;
-
- time += bl;
+ time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
currentBeat++;
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 1628423fe8..ad203d2107 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats
@@ -211,5 +212,41 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
}
}
+
+ [Test]
+ public void TestDecodeCustomSamples()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+ using (var resStream = Resource.OpenResource("custom-samples.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var hitObjects = decoder.Decode(stream).HitObjects;
+
+ Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[0]).Name);
+ Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[1]).Name);
+ Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name);
+ Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[3]).Name);
+ }
+
+ SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
+ }
+
+ [Test]
+ public void TestDecodeCustomHitObjectSamples()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+ using (var resStream = Resource.OpenResource("custom-hitobject-samples.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var hitObjects = decoder.Decode(stream).HitObjects;
+
+ Assert.AreEqual("hit_1.wav", hitObjects[0].Samples[0].LookupNames.First());
+ Assert.AreEqual("hit_2.wav", hitObjects[1].Samples[0].LookupNames.First());
+ Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name);
+ Assert.AreEqual("hit_1.wav", hitObjects[3].Samples[0].LookupNames.First());
+ }
+
+ SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index b834be71f1..64bd563897 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -118,7 +118,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestParity(string beatmap)
{
var legacy = decode(beatmap, out Beatmap json);
- json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert();
+ json.WithDeepEqual(legacy)
+ .IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
+ // Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
+ || r.Name == nameof(LegacyDecoder.LegacySampleControlPoint.CustomSampleBank))
+ .Assert();
}
///
diff --git a/osu.Game.Tests/Resources/custom-hitobject-samples.osu b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
new file mode 100644
index 0000000000..588672e2d9
--- /dev/null
+++ b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+
+[TimingPoints]
+2170,468.75,4,1,0,40,1,0
+2638,-100,4,1,1,40,0,0
+3107,-100,4,1,2,40,0,0
+3576,-100,4,1,0,40,0,0
+
+[HitObjects]
+255,193,2170,1,0,0:0:0:0:hit_1.wav
+256,191,2638,5,0,0:0:0:0:hit_2.wav
+255,193,3107,1,0,0:0:0:0:
+256,191,3576,1,0,0:0:0:0:hit_1.wav
diff --git a/osu.Game.Tests/Resources/custom-samples.osu b/osu.Game.Tests/Resources/custom-samples.osu
new file mode 100644
index 0000000000..1e0e6f558e
--- /dev/null
+++ b/osu.Game.Tests/Resources/custom-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+
+[TimingPoints]
+2170,468.75,4,1,0,40,1,0
+2638,-100,4,1,1,40,0,0
+3107,-100,4,1,2,40,0,0
+3576,-100,4,1,0,40,0,0
+
+[HitObjects]
+255,193,2170,1,0,0:0:0:0:
+256,191,2638,5,0,0:0:0:0:
+255,193,3107,1,0,0:0:0:0:
+256,191,3576,1,0,0:0:0:0:
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index f52fecfd02..ee66f53ddc 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -65,17 +65,19 @@ namespace osu.Game.Tests.Visual
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
- var ruleset = rulesetInfo.CreateInstance();
+ var instance = rulesetInfo.CreateInstance();
var testBeatmap = createTestBeatmap(rulesetInfo);
beatmaps.Add(testBeatmap);
+ AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
+
selectBeatmap(testBeatmap);
- testBeatmapLabels(ruleset);
+ testBeatmapLabels(instance);
// TODO: adjust cases once more info is shown for other gamemodes
- switch (ruleset)
+ switch (instance)
{
case OsuRuleset _:
testInfoLabels(5);
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
index 94b99d483c..dace6e20ef 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
@@ -1,8 +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 System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -10,9 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using OpenTK;
@@ -20,10 +16,9 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorSeekSnapping : EditorClockTestCase
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectComposer) };
-
public TestCaseEditorSeekSnapping()
{
BeatDivisor.Value = 4;
@@ -56,22 +51,13 @@ namespace osu.Game.Tests.Visual
Beatmap.Value = new TestWorkingBeatmap(testBeatmap);
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
-
- testSeekNoSnapping();
- testSeekSnappingOnBeat();
- testSeekSnappingInBetweenBeat();
- testSeekForwardNoSnapping();
- testSeekForwardSnappingOnBeat();
- testSeekForwardSnappingFromInBetweenBeat();
- testSeekBackwardSnappingOnBeat();
- testSeekBackwardSnappingFromInBetweenBeat();
- testSeekingWithFloatingPointBeatLength();
}
///
/// Tests whether time is correctly seeked without snapping.
///
- private void testSeekNoSnapping()
+ [Test]
+ public void TestSeekNoSnapping()
{
reset();
@@ -94,7 +80,8 @@ namespace osu.Game.Tests.Visual
/// Tests whether seeking to exact beat times puts us on the beat time.
/// These are the white/yellow ticks on the graph.
///
- private void testSeekSnappingOnBeat()
+ [Test]
+ public void TestSeekSnappingOnBeat()
{
reset();
@@ -117,9 +104,9 @@ namespace osu.Game.Tests.Visual
///
/// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats.
/// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too.
- /// If
///
- private void testSeekSnappingInBetweenBeat()
+ [Test]
+ public void TestSeekSnappingInBetweenBeat()
{
reset();
@@ -140,7 +127,8 @@ namespace osu.Game.Tests.Visual
///
/// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
///
- private void testSeekForwardNoSnapping()
+ [Test]
+ public void TestSeekForwardNoSnapping()
{
reset();
@@ -159,7 +147,8 @@ namespace osu.Game.Tests.Visual
///
/// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped.
///
- private void testSeekForwardSnappingOnBeat()
+ [Test]
+ public void TestSeekForwardSnappingOnBeat()
{
reset();
@@ -181,7 +170,8 @@ namespace osu.Game.Tests.Visual
/// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped.
/// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue.
///
- private void testSeekForwardSnappingFromInBetweenBeat()
+ [Test]
+ public void TestSeekForwardSnappingFromInBetweenBeat()
{
reset();
@@ -214,21 +204,20 @@ namespace osu.Game.Tests.Visual
///
/// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
///
- private void testSeekBackwardNoSnapping()
+ [Test]
+ public void TestSeekBackwardNoSnapping()
{
reset();
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 425", () => Clock.CurrentTime == 425);
+ AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 375", () => Clock.CurrentTime == 375);
+ AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 325", () => Clock.CurrentTime == 325);
+ AddAssert("Time = 150", () => Clock.CurrentTime == 150);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 125", () => Clock.CurrentTime == 125);
- AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 25", () => Clock.CurrentTime == 25);
+ AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
}
@@ -236,7 +225,8 @@ namespace osu.Game.Tests.Visual
///
/// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped.
///
- private void testSeekBackwardSnappingOnBeat()
+ [Test]
+ public void TestSeekBackwardSnappingOnBeat()
{
reset();
@@ -259,7 +249,8 @@ namespace osu.Game.Tests.Visual
/// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped.
/// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue.
///
- private void testSeekBackwardSnappingFromInBetweenBeat()
+ [Test]
+ public void TestSeekBackwardSnappingFromInBetweenBeat()
{
reset();
@@ -280,7 +271,8 @@ namespace osu.Game.Tests.Visual
///
/// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength.
///
- private void testSeekingWithFloatingPointBeatLength()
+ [Test]
+ public void TestSeekingWithFloatingPointBeatLength()
{
reset();
@@ -288,7 +280,7 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(0)", () => Clock.Seek(0));
- for (int i = 0; i < 20; i++)
+ for (int i = 0; i < 9; i++)
{
AddStep("SeekForward, Snap", () =>
{
@@ -298,7 +290,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime);
}
- for (int i = 0; i < 20; i++)
+ for (int i = 0; i < 9; i++)
{
AddStep("SeekBackward, Snap", () =>
{
@@ -316,16 +308,6 @@ namespace osu.Game.Tests.Visual
AddStep("Reset", () => Clock.Seek(0));
}
- private class TestHitObjectComposer : HitObjectComposer
- {
- public TestHitObjectComposer(Ruleset ruleset)
- : base(ruleset)
- {
- }
-
- protected override IReadOnlyList CompositionTools => new ICompositionTool[0];
- }
-
private class TimingPointVisualiser : CompositeDrawable
{
private readonly double length;
diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
index 7f5bce3b84..e8ac19e7fc 100644
--- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
@@ -1,6 +1,8 @@
// 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.ComponentModel;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
@@ -17,6 +19,12 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[] {
+ typeof(Placeholder),
+ typeof(MessagePlaceholder),
+ typeof(RetrievalFailurePlaceholder),
+ };
+
private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard;
diff --git a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs
new file mode 100644
index 0000000000..ebbc673d36
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs
@@ -0,0 +1,63 @@
+// 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.Shapes;
+using osu.Framework.Testing;
+using osu.Game.Graphics.UserInterface;
+using OpenTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseLoadingAnimation : GridTestCase
+ {
+ public TestCaseLoadingAnimation()
+ : base(2, 2)
+ {
+ LoadingAnimation loading;
+
+ Cell(0).AddRange(new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both
+ },
+ loading = new LoadingAnimation()
+ });
+
+ loading.Show();
+
+ Cell(1).AddRange(new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both
+ },
+ loading = new LoadingAnimation()
+ });
+
+ loading.Show();
+
+ Cell(2).AddRange(new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Gray,
+ RelativeSizeAxes = Axes.Both
+ },
+ loading = new LoadingAnimation()
+ });
+
+ loading.Show();
+
+ Cell(3).AddRange(new Drawable[]
+ {
+ loading = new LoadingAnimation()
+ });
+
+ Scheduler.AddDelayed(() => loading.ToggleVisibility(), 200, true);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index 3255478bea..73c37348d5 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
Ruleset ruleset = rulesetInfo.CreateInstance();
- AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo);
+ AddStep($"switch to {ruleset.Description}", () => Ruleset.Value = rulesetInfo);
switch (ruleset)
{
diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
index 8f90b6d87e..9b48ec17bd 100644
--- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual
private class TestPlayfield : ScrollingPlayfield
{
- public readonly ScrollingDirection Direction;
+ public new readonly ScrollingDirection Direction;
public TestPlayfield(ScrollingDirection direction)
: base(direction)
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index f635b74030..7e329ac651 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
namespace osu.Game.Audio
{
@@ -32,5 +33,21 @@ namespace osu.Game.Audio
/// The sample volume.
///
public int Volume;
+
+ ///
+ /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
+ ///
+ public virtual IEnumerable LookupNames
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Namespace))
+ yield return $"{Namespace}/{Bank}-{Name}";
+
+ yield return $"{Bank}-{Name}"; // Without namespace as a fallback even when we have a namespace
+ }
+ }
+
+ public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 3afc3c4d32..303a19aab3 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -66,8 +66,8 @@ namespace osu.Game.Beatmaps
// General
public int AudioLeadIn { get; set; }
- public bool Countdown { get; set; }
- public float StackLeniency { get; set; }
+ public bool Countdown { get; set; } = true;
+ public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }
public int RulesetID { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 50428cc5e6..e488dacf80 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps
PostNotification?.Invoke(new SimpleNotification
{
Icon = FontAwesome.fa_superpowers,
- Text = "You gotta be a supporter to download for now 'yo"
+ Text = "You gotta be an osu!supporter to download for now 'yo"
});
return;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 71406c6034..43ae30f780 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -69,11 +69,22 @@ namespace osu.Game.Beatmaps
}
catch
{
- return new TrackVirtual();
+ return null;
}
}
- protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
+ protected override Waveform GetWaveform()
+ {
+ try
+ {
+ var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
+ return trackData == null ? null : new Waveform(trackData);
+ }
+ catch
+ {
+ return null;
+ }
+ }
protected override Storyboard GetStoryboard()
{
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index bf1cd7d4ee..0173125e8b 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -6,23 +6,9 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
- public interface IBeatmapProcessor
- {
- IBeatmap Beatmap { get; }
-
- ///
- /// Post-processes to add mode-specific components that aren't added during conversion.
- ///
- /// An example of such a usage is for combo colours.
- ///
- ///
- void PostProcess();
- }
-
///
- /// Processes a post-converted Beatmap.
+ /// Provides functionality to alter a after it has been converted.
///
- /// The type of HitObject contained in the Beatmap.
public class BeatmapProcessor : IBeatmapProcessor
{
public IBeatmap Beatmap { get; }
@@ -32,13 +18,7 @@ namespace osu.Game.Beatmaps
Beatmap = beatmap;
}
- ///
- /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
- ///
- /// An example of such a usage is for combo colours.
- ///
- ///
- public virtual void PostProcess()
+ public virtual void PreProcess()
{
IHasComboInformation lastObj = null;
@@ -62,5 +42,9 @@ namespace osu.Game.Beatmaps
lastObj = obj;
}
}
+
+ public virtual void PostProcess()
+ {
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index ed8fbdbb26..ebebe42097 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -13,7 +13,13 @@ namespace osu.Game.Beatmaps
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- public int? OnlineBeatmapSetID { get; set; }
+ private int? onlineBeatmapSetID;
+
+ public int? OnlineBeatmapSetID
+ {
+ get { return onlineBeatmapSetID; }
+ set { onlineBeatmapSetID = value > 0 ? value : null; }
+ }
public BeatmapMetadata Metadata { get; set; }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index db9e712d86..9ed476d97c 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -14,6 +14,15 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
- public bool Equals(ControlPoint other) => Time.Equals(other?.Time);
+ ///
+ /// Whether this provides the same parametric changes as another .
+ /// Basically an equality check without considering the .
+ ///
+ /// The to compare to.
+ /// Whether this is equivalent to .
+ public virtual bool EquivalentTo(ControlPoint other) => true;
+
+ public bool Equals(ControlPoint other)
+ => EquivalentTo(other) && Time.Equals(other?.Time);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 9f717d21e3..526bddf51a 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -17,5 +17,10 @@ namespace osu.Game.Beatmaps.ControlPoints
}
private double speedMultiplier = 1;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is DifficultyControlPoint difficulty
+ && SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 73d5232f44..dd9d568133 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -14,5 +14,11 @@ namespace osu.Game.Beatmaps.ControlPoints
/// Whether the first bar line of this control point is ignored.
///
public bool OmitFirstBarLine;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is EffectControlPoint effect
+ && KiaiMode.Equals(effect.KiaiMode)
+ && OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 5d801a1163..acccbcde46 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -30,5 +30,25 @@ namespace osu.Game.Beatmaps.ControlPoints
Name = sampleName,
Volume = SampleVolume,
};
+
+ ///
+ /// Applies and to a if necessary, returning the modified .
+ ///
+ /// The . This will not be modified.
+ /// The modified . This does not share a reference with .
+ public virtual SampleInfo ApplyTo(SampleInfo sampleInfo)
+ {
+ var newSampleInfo = sampleInfo.Clone();
+ newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank;
+ newSampleInfo.Name = sampleInfo.Name;
+ newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume;
+ return newSampleInfo;
+ }
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is SampleControlPoint sample
+ && SampleBank.Equals(sample.SampleBank)
+ && SampleVolume.Equals(sample.SampleVolume);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index d20b1b87a6..eb60133fed 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -23,5 +23,11 @@ namespace osu.Game.Beatmaps.ControlPoints
}
private double beatLength = 1000;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is TimingControlPoint timing
+ && TimeSignature.Equals(timing.TimeSignature)
+ && BeatLength.Equals(timing.BeatLength);
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 265c6832b2..25a76b52a7 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4");
- protected override Track GetTrack() => new TrackVirtual();
+ protected override Track GetTrack() => new TrackVirtual { Length = 1000 };
private class DummyRulesetInfo : RulesetInfo
{
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 581207607a..c79938e613 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -289,9 +289,9 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]);
- //SampleBank sampleBank = SampleBank.Default;
- //if (split.Length >= 5)
- // sampleBank = (SampleBank)int.Parse(split[4]);
+ int customSampleBank = 0;
+ if (split.Length >= 5)
+ customSampleBank = int.Parse(split[4]);
int sampleVolume = defaultSampleVolume;
if (split.Length >= 6)
@@ -314,13 +314,9 @@ namespace osu.Game.Beatmaps.Formats
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
- DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
- SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
- EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
-
if (timingChange)
{
- beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
+ handleTimingControlPoint(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
@@ -328,41 +324,68 @@ namespace osu.Game.Beatmaps.Formats
});
}
- if (speedMultiplier != difficultyPoint.SpeedMultiplier)
+ handleDifficultyControlPoint(new DifficultyControlPoint
{
- beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
- beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
- {
- Time = time,
- SpeedMultiplier = speedMultiplier
- });
- }
+ Time = time,
+ SpeedMultiplier = speedMultiplier
+ });
- if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume)
+ handleEffectControlPoint(new EffectControlPoint
{
- beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint
- {
- Time = time,
- SampleBank = stringSampleSet,
- SampleVolume = sampleVolume
- });
- }
+ Time = time,
+ KiaiMode = kiaiMode,
+ OmitFirstBarLine = omitFirstBarSignature
+ });
- if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
+ handleSampleControlPoint(new LegacySampleControlPoint
{
- beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
- {
- Time = time,
- KiaiMode = kiaiMode,
- OmitFirstBarLine = omitFirstBarSignature
- });
- }
+ Time = time,
+ SampleBank = stringSampleSet,
+ SampleVolume = sampleVolume,
+ CustomSampleBank = customSampleBank
+ });
}
catch (FormatException e)
{
}
}
+ private void handleTimingControlPoint(TimingControlPoint newPoint)
+ {
+ beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
+ }
+
+ private void handleDifficultyControlPoint(DifficultyControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time);
+ beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
+ }
+
+ private void handleEffectControlPoint(EffectControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
+ }
+
+ private void handleSampleControlPoint(SampleControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
+ }
+
private void handleHitObject(string line)
{
// If the ruleset wasn't specified, assume the osu!standard ruleset.
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e77efd8508..22a6acf459 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Logging;
+using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats
@@ -167,5 +169,25 @@ namespace osu.Game.Beatmaps.Formats
Pass = 2,
Foreground = 3
}
+
+ internal class LegacySampleControlPoint : SampleControlPoint
+ {
+ public int CustomSampleBank;
+
+ public override SampleInfo ApplyTo(SampleInfo sampleInfo)
+ {
+ var baseInfo = base.ApplyTo(sampleInfo);
+
+ if (CustomSampleBank > 1)
+ baseInfo.Name += CustomSampleBank;
+
+ return baseInfo;
+ }
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is LegacySampleControlPoint legacy
+ && CustomSampleBank == legacy.CustomSampleBank;
+ }
}
}
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index 00566093b8..cbf9d184ac 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -7,6 +7,9 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
{
+ ///
+ /// Provides functionality to convert a for a .
+ ///
public interface IBeatmapConverter
{
///
diff --git a/osu.Game/Beatmaps/IBeatmapProcessor.cs b/osu.Game/Beatmaps/IBeatmapProcessor.cs
new file mode 100644
index 0000000000..282662373a
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapProcessor.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// Provides functionality to alter a after it has been converted.
+ ///
+ public interface IBeatmapProcessor
+ {
+ ///
+ /// The to process. This should already be converted to the applicable .
+ ///
+ IBeatmap Beatmap { get; }
+
+ ///
+ /// Processes the converted prior to being invoked.
+ ///
+ /// Nested s generated during will not be present by this point,
+ /// and no mods will have been applied to the s.
+ ///
+ ///
+ ///
+ /// This can only be used to add alterations to s generated directly through the conversion process.
+ ///
+ void PreProcess();
+
+ ///
+ /// Processes the converted after has been invoked.
+ ///
+ /// Nested s generated during will be present by this point,
+ /// and mods will have been applied to all s.
+ ///
+ ///
+ ///
+ /// This should be used to add alterations to s while they are in their most playable state.
+ ///
+ void PostProcess();
+ }
+}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 4310d9b7df..74da978d9c 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -19,7 +19,7 @@ using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
- public abstract class WorkingBeatmap : IDisposable
+ public abstract partial class WorkingBeatmap : IDisposable
{
public readonly BeatmapInfo BeatmapInfo;
@@ -116,8 +116,9 @@ namespace osu.Game.Beatmaps
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
}
- // Post-process
- rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
+ IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted);
+
+ processor?.PreProcess();
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects)
@@ -127,6 +128,8 @@ namespace osu.Game.Beatmaps
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
+ processor?.PostProcess();
+
return converted;
}
@@ -145,7 +148,7 @@ namespace osu.Game.Beatmaps
private Track populateTrack()
{
// we want to ensure that we always have a track, even if it's a fake one.
- var t = GetTrack() ?? new TrackVirtual();
+ var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
applyRateAdjustments(t);
return t;
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs
new file mode 100644
index 0000000000..0e0a9a3bb9
--- /dev/null
+++ b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs
@@ -0,0 +1,38 @@
+// 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.Framework.Audio.Track;
+using osu.Game.Rulesets.Objects.Types;
+
+namespace osu.Game.Beatmaps
+{
+ public partial class WorkingBeatmap
+ {
+ ///
+ /// A type of which provides a valid length based on the s of an .
+ ///
+ protected class VirtualBeatmapTrack : TrackVirtual
+ {
+ private const double excess_length = 1000;
+
+ public VirtualBeatmapTrack(IBeatmap beatmap)
+ {
+ var lastObject = beatmap.HitObjects.LastOrDefault();
+
+ switch (lastObject)
+ {
+ case null:
+ Length = excess_length;
+ break;
+ case IHasEndTime endTime:
+ Length = endTime.EndTime + excess_length;
+ break;
+ default:
+ Length = lastObject.StartTime + excess_length;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index d6b6595b69..39369350ef 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -8,16 +8,20 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Configuration;
+using osu.Framework.Input.Bindings;
using osu.Game.Audio;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers
{
- public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
+ public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler
{
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
+ protected virtual bool PlaySamplesOnStateChange => true;
+
private PreviewTrackManager previewTrackManager;
protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
@@ -63,18 +67,33 @@ namespace osu.Game.Graphics.Containers
return base.OnClick(state);
}
+ public bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ State = Visibility.Hidden;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => false;
+
private void onStateChanged(Visibility visibility)
{
switch (visibility)
{
case Visibility.Visible:
if (OverlayActivationMode != OverlayActivation.Disabled)
- samplePopIn?.Play();
+ {
+ if (PlaySamplesOnStateChange) samplePopIn?.Play();
+ }
else
State = Visibility.Hidden;
break;
case Visibility.Hidden:
- samplePopOut?.Play();
+ if (PlaySamplesOnStateChange) samplePopOut?.Play();
break;
}
}
diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
index 119af4d762..e77e075fe2 100644
--- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
@@ -2,6 +2,7 @@
// 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.Sprites;
using osu.Game.Graphics.Sprites;
@@ -16,6 +17,8 @@ namespace osu.Game.Graphics.Containers
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
+ public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable);
+
public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters);
}
}
diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs
index 5f57fb76b0..0bb33930b0 100644
--- a/osu.Game/Graphics/Cursor/MenuCursor.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursor.cs
@@ -11,9 +11,9 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Configuration;
using System;
-using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Graphics.Textures;
+using OpenTK.Input;
namespace osu.Game.Graphics.Cursor
{
@@ -25,9 +25,8 @@ namespace osu.Game.Graphics.Cursor
protected override Drawable CreateCursor() => new Cursor();
private Bindable cursorRotate;
- private bool dragging;
-
- private bool startRotation;
+ private DragRotationState dragRotationState;
+ private Vector2 positionMouseDown;
[BackgroundDependencyLoader(true)]
private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager)
@@ -40,18 +39,18 @@ namespace osu.Game.Graphics.Cursor
protected override bool OnMouseMove(InputState state)
{
- if (cursorRotate && dragging)
+ if (dragRotationState != DragRotationState.NotDragging)
{
- Debug.Assert(state.Mouse.PositionMouseDown != null);
-
+ var position = state.Mouse.Position;
+ var distance = Vector2Extensions.Distance(position, positionMouseDown);
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
- // ReSharper disable once PossibleInvalidOperationException
- startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
-
- if (startRotation)
+ if (dragRotationState == DragRotationState.DragStarted && distance > 30)
+ dragRotationState = DragRotationState.Rotating;
+ // don't rotate when distance is zero to avoid NaN
+ if (dragRotationState == DragRotationState.Rotating && distance > 0)
{
- Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
+ Vector2 offset = state.Mouse.Position - positionMouseDown;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
@@ -67,12 +66,6 @@ namespace osu.Game.Graphics.Cursor
return base.OnMouseMove(state);
}
- protected override bool OnDragStart(InputState state)
- {
- dragging = true;
- return base.OnDragStart(state);
- }
-
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
ActiveCursor.Scale = new Vector2(1);
@@ -80,6 +73,12 @@ namespace osu.Game.Graphics.Cursor
((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0;
((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
+
+ if (args.Button == MouseButton.Left && cursorRotate)
+ {
+ dragRotationState = DragRotationState.DragStarted;
+ positionMouseDown = state.Mouse.Position;
+ }
return base.OnMouseDown(state, args);
}
@@ -87,14 +86,16 @@ namespace osu.Game.Graphics.Cursor
{
if (!state.Mouse.HasMainButtonPressed)
{
- dragging = false;
- startRotation = false;
-
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint);
- ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);
ActiveCursor.ScaleTo(1, 500, Easing.OutElastic);
}
+ if (args.Button == MouseButton.Left)
+ {
+ if (dragRotationState == DragRotationState.Rotating)
+ ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);
+ dragRotationState = DragRotationState.NotDragging;
+ }
return base.OnMouseUp(state, args);
}
@@ -160,5 +161,12 @@ namespace osu.Game.Graphics.Cursor
cursorScale.TriggerChange();
}
}
+
+ private enum DragRotationState
+ {
+ NotDragging,
+ DragStarted,
+ Rotating,
+ }
}
}
diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs
index 763e57e397..406fc2ffd2 100644
--- a/osu.Game/Graphics/DrawableDate.cs
+++ b/osu.Game/Graphics/DrawableDate.cs
@@ -12,14 +12,14 @@ namespace osu.Game.Graphics
{
public class DrawableDate : OsuSpriteText, IHasTooltip
{
- private readonly DateTimeOffset date;
+ protected readonly DateTimeOffset Date;
public DrawableDate(DateTimeOffset date)
{
AutoSizeAxes = Axes.Both;
Font = "Exo2.0-RegularItalic";
- this.date = date.ToLocalTime();
+ Date = date.ToLocalTime();
}
[BackgroundDependencyLoader]
@@ -38,7 +38,7 @@ namespace osu.Game.Graphics
{
updateTime();
- var diffToNow = DateTimeOffset.Now.Subtract(date);
+ var diffToNow = DateTimeOffset.Now.Subtract(Date);
double timeUntilNextUpdate = 1000;
if (diffToNow.TotalSeconds > 60)
@@ -58,8 +58,10 @@ namespace osu.Game.Graphics
public override bool HandleMouseInput => true;
- private void updateTime() => Text = date.Humanize();
+ protected virtual string Format() => Date.Humanize();
- public string TooltipText => date.ToString();
+ private void updateTime() => Text = Format();
+
+ public virtual string TooltipText => string.Format($"{Date:MMMM d, yyyy h:mm tt \"UTC\"z}");
}
}
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index 6500578de3..28d04c9a82 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
-using OpenTK.Input;
using osu.Framework.Input;
using System;
+using osu.Game.Input.Bindings;
+using OpenTK.Input;
namespace osu.Game.Graphics.UserInterface
{
@@ -19,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface
public Action Exit;
private bool focus;
+
public bool HoldFocus
{
get { return focus; }
@@ -26,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
{
focus = value;
if (!focus && HasFocus)
- GetContainingInputManager().ChangeFocus(null);
+ base.KillFocus();
}
}
@@ -41,18 +43,34 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
- if (!args.Repeat && args.Key == Key.Escape)
- {
- if (Text.Length > 0)
- Text = string.Empty;
- else
- Exit?.Invoke();
- return true;
- }
+ if (!HasFocus) return false;
+
+ if (args.Key == Key.Escape)
+ return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back).
return base.OnKeyDown(state, args);
}
+ public override bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ if (Text.Length > 0)
+ {
+ Text = string.Empty;
+ return true;
+ }
+ }
+
+ return base.OnPressed(action);
+ }
+
+ protected override void KillFocus()
+ {
+ base.KillFocus();
+ Exit?.Invoke();
+ }
+
public override bool RequestsFocus => HoldFocus;
}
}
diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
index 5ea6bce432..8d45b03a43 100644
--- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
@@ -4,12 +4,17 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public class LoadingAnimation : VisibilityContainer
{
private readonly SpriteIcon spinner;
+ private readonly SpriteIcon spinnerShadow;
+
+ private const float spin_duration = 600;
+ private const float transition_duration = 200;
public LoadingAnimation()
{
@@ -20,12 +25,22 @@ namespace osu.Game.Graphics.UserInterface
Children = new Drawable[]
{
- spinner = new SpriteIcon
+ spinnerShadow = new SpriteIcon
{
- Size = new Vector2(20),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Icon = FontAwesome.fa_spinner
+ RelativeSizeAxes = Axes.Both,
+ Position = new Vector2(1, 1),
+ Colour = Color4.Black,
+ Alpha = 0.4f,
+ Icon = FontAwesome.fa_circle_o_notch
+ },
+ spinner = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Icon = FontAwesome.fa_circle_o_notch
}
};
}
@@ -34,12 +49,11 @@ namespace osu.Game.Graphics.UserInterface
{
base.LoadComplete();
- spinner.Spin(2000, RotationDirection.Clockwise);
+ spinner.Spin(spin_duration, RotationDirection.Clockwise);
+ spinnerShadow.Spin(spin_duration, RotationDirection.Clockwise);
}
- private const float transition_duration = 500;
-
- protected override void PopIn() => this.FadeIn(transition_duration * 5, Easing.OutQuint);
+ protected override void PopIn() => this.FadeIn(transition_duration * 2, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
}
diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
index d34d2b2a7c..07920865c0 100644
--- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface
{
protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize);
- public override bool AllowClipboardExport => false;
+ protected override bool AllowClipboardExport => false;
private readonly CapsWarning warning;
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index 6021af2028..88b0543de0 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -9,10 +9,12 @@ using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface
{
- public class OsuTextBox : TextBox
+ public class OsuTextBox : TextBox, IKeyBindingHandler
{
protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f);
protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f);
@@ -33,10 +35,7 @@ namespace osu.Game.Graphics.UserInterface
TextContainer.Height = 0.5f;
CornerRadius = 5;
- Current.DisabledChanged += disabled =>
- {
- Alpha = disabled ? 0.3f : 1;
- };
+ Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
}
[BackgroundDependencyLoader]
@@ -59,5 +58,18 @@ namespace osu.Game.Graphics.UserInterface
}
protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize };
+
+ public virtual bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ KillFocus();
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => false;
}
}
diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
index e50539e120..7d53c9e9d9 100644
--- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
@@ -34,8 +34,6 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
- if (HandlePendingText(state)) return true;
-
if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed)
{
switch (args.Key)
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index fced2e5d95..83ffd415ae 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -45,7 +45,9 @@ namespace osu.Game.Input.Bindings
public IEnumerable InGameKeyBindings => new[]
{
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
- new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry)
+ new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry),
+ new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
+ new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
};
protected override IEnumerable KeyBindingInputQueue =>
@@ -85,6 +87,12 @@ namespace osu.Game.Input.Bindings
ToggleGameplayMouseButtons,
[Description("Go back")]
- Back
+ Back,
+
+ [Description("Increase scroll speed")]
+ IncreaseScrollSpeed,
+
+ [Description("Decrease scroll speed")]
+ DecreaseScrollSpeed,
}
}
diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs
new file mode 100644
index 0000000000..7eeacd56d7
--- /dev/null
+++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs
@@ -0,0 +1,376 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20180628011956_RemoveNegativeSetIDs")]
+ partial class RemoveNegativeSetIDs
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntKey")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs
new file mode 100644
index 0000000000..c38cd19b42
--- /dev/null
+++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class RemoveNegativeSetIDs : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ // There was a change that baetmaps were being loaded with "-1" online IDs, which is completely incorrect.
+ // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps.
+ migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 8c5fc58878..12935a5ffe 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -148,7 +148,7 @@ namespace osu.Game.Online.API
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
// before actually going online.
- while (State != APIState.Online)
+ while (State > APIState.Offline && State < APIState.Online)
Thread.Sleep(500);
break;
@@ -158,7 +158,6 @@ namespace osu.Game.Online.API
if (authentication.RequestAccessToken() == null)
{
Logout(false);
- State = APIState.Offline;
continue;
}
@@ -208,6 +207,14 @@ namespace osu.Game.Online.API
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
+ // special cases for un-typed but useful message responses.
+ switch (we.Message)
+ {
+ case "Unauthorized":
+ statusCode = HttpStatusCode.Unauthorized;
+ break;
+ }
+
switch (statusCode)
{
case HttpStatusCode.Unauthorized:
@@ -292,6 +299,7 @@ namespace osu.Game.Online.API
password = null;
authentication.Clear();
LocalUser.Value = createGuestUser();
+ State = APIState.Offline;
}
private static User createGuestUser() => new User
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
index 3b6bb565b0..bbaaa0c756 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Online.API.Requests.Responses
private DateTimeOffset submitted { get; set; }
[JsonProperty(@"ranked_date")]
- private DateTimeOffset ranked { get; set; }
+ private DateTimeOffset? ranked { get; set; }
[JsonProperty(@"last_updated")]
private DateTimeOffset lastUpdated { get; set; }
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index ba8685b5b2..922d228701 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -85,7 +85,7 @@ namespace osu.Game
private OnScreenDisplay onscreenDisplay;
private Bindable configRuleset;
- public Bindable Ruleset = new Bindable();
+ private readonly Bindable ruleset = new Bindable();
private Bindable configSkin;
@@ -147,10 +147,13 @@ namespace osu.Game
dependencies.CacheAs(this);
+ dependencies.CacheAs(ruleset);
+ dependencies.CacheAs>(ruleset);
+
// bind config int to database RulesetInfo
configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset);
- Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
- Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0;
+ ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
+ ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0;
// bind config int to database SkinInfo
configSkin = LocalConfig.GetBindable(OsuSetting.Skin);
@@ -216,7 +219,7 @@ namespace osu.Game
return;
}
- Ruleset.Value = s.Ruleset;
+ ruleset.Value = s.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap);
Beatmap.Value.Mods.Value = s.Mods;
@@ -247,7 +250,8 @@ namespace osu.Game
new VolumeControlReceptor
{
RelativeSizeAxes = Axes.Both,
- ActionRequested = action => volume.Adjust(action)
+ ActionRequested = action => volume.Adjust(action),
+ ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
},
mainContent = new Container { RelativeSizeAxes = Axes.Both },
overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },
@@ -550,7 +554,7 @@ namespace osu.Game
// the use case for not applying is in visual/unit tests.
bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false;
- Ruleset.Disabled = applyRestrictions;
+ ruleset.Disabled = applyRestrictions;
Beatmap.Disabled = applyRestrictions;
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs
index 5f90ec67ad..cd0913a25e 100644
--- a/osu.Game/Overlays/Dialog/PopupDialog.cs
+++ b/osu.Game/Overlays/Dialog/PopupDialog.cs
@@ -6,16 +6,16 @@ using System.Linq;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Dialog
{
@@ -23,6 +23,9 @@ namespace osu.Game.Overlays.Dialog
{
public static readonly float ENTER_DURATION = 500;
public static readonly float EXIT_DURATION = 200;
+
+ protected override bool BlockPassThroughMouse => false;
+
private readonly Vector2 ringSize = new Vector2(100f);
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
@@ -34,26 +37,28 @@ namespace osu.Game.Overlays.Dialog
private readonly SpriteText header;
private readonly TextFlowContainer body;
+ private bool actionInvoked;
+
public FontAwesome Icon
{
- get { return icon.Icon; }
- set { icon.Icon = value; }
+ get => icon.Icon;
+ set => icon.Icon = value;
}
public string HeaderText
{
- get { return header.Text; }
- set { header.Text = value; }
+ get => header.Text;
+ set => header.Text = value;
}
public string BodyText
{
- set { body.Text = value; }
+ set => body.Text = value;
}
public IEnumerable Buttons
{
- get { return buttonsContainer.Children; }
+ get => buttonsContainer.Children;
set
{
buttonsContainer.ChildrenEnumerable = value;
@@ -62,71 +67,17 @@ namespace osu.Game.Overlays.Dialog
var action = b.Action;
b.Action = () =>
{
- Hide();
+ if (actionInvoked) return;
+
+ actionInvoked = true;
action?.Invoke();
+
+ Hide();
};
}
}
}
- private void pressButtonAtIndex(int index)
- {
- if (index < Buttons.Count())
- Buttons.Skip(index).First().TriggerOnClick();
- }
-
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
- {
- if (args.Repeat) return false;
-
- if (args.Key == Key.Enter || args.Key == Key.KeypadEnter)
- {
- Buttons.OfType().FirstOrDefault()?.TriggerOnClick();
- return true;
- }
-
- // press button at number if 1-9 on number row or keypad are pressed
- var k = args.Key;
- if (k >= Key.Number1 && k <= Key.Number9)
- {
- pressButtonAtIndex(k - Key.Number1);
- return true;
- }
-
- if (k >= Key.Keypad1 && k <= Key.Keypad9)
- {
- pressButtonAtIndex(k - Key.Keypad1);
- return true;
- }
-
- return base.OnKeyDown(state, args);
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- // Reset various animations but only if the dialog animation fully completed
- if (content.Alpha == 0)
- {
- buttonsContainer.TransformSpacingTo(buttonsEnterSpacing);
- buttonsContainer.MoveToY(buttonsEnterSpacing.Y);
- ring.ResizeTo(ringMinifiedSize);
- }
-
- content.FadeIn(ENTER_DURATION, Easing.OutQuint);
- ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint);
- buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint);
- buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint);
- }
-
- protected override void PopOut()
- {
- base.PopOut();
-
- content.FadeOut(EXIT_DURATION, Easing.InSine);
- }
-
public PopupDialog()
{
RelativeSizeAxes = Axes.Both;
@@ -136,9 +87,6 @@ namespace osu.Game.Overlays.Dialog
content = new Container
{
RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Width = 0.4f,
Alpha = 0f,
Children = new Drawable[]
{
@@ -243,5 +191,69 @@ namespace osu.Game.Overlays.Dialog
},
};
}
+
+ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ {
+ if (args.Repeat) return false;
+
+ if (args.Key == Key.Enter || args.Key == Key.KeypadEnter)
+ {
+ Buttons.OfType().FirstOrDefault()?.TriggerOnClick();
+ return true;
+ }
+
+ // press button at number if 1-9 on number row or keypad are pressed
+ var k = args.Key;
+ if (k >= Key.Number1 && k <= Key.Number9)
+ {
+ pressButtonAtIndex(k - Key.Number1);
+ return true;
+ }
+
+ if (k >= Key.Keypad1 && k <= Key.Keypad9)
+ {
+ pressButtonAtIndex(k - Key.Keypad1);
+ return true;
+ }
+
+ return base.OnKeyDown(state, args);
+ }
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+
+ actionInvoked = false;
+
+ // Reset various animations but only if the dialog animation fully completed
+ if (content.Alpha == 0)
+ {
+ buttonsContainer.TransformSpacingTo(buttonsEnterSpacing);
+ buttonsContainer.MoveToY(buttonsEnterSpacing.Y);
+ ring.ResizeTo(ringMinifiedSize);
+ }
+
+ content.FadeIn(ENTER_DURATION, Easing.OutQuint);
+ ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint);
+ buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint);
+ buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint);
+ }
+
+ protected override void PopOut()
+ {
+ if (!actionInvoked)
+ // In the case a user did not choose an action before a hide was triggered, press the last button.
+ // This is presumed to always be a sane default "cancel" action.
+ buttonsContainer.Last().TriggerOnClick();
+
+ base.PopOut();
+ content.FadeOut(EXIT_DURATION, Easing.InSine);
+ }
+
+ private void pressButtonAtIndex(int index)
+ {
+ if (index < Buttons.Count())
+ Buttons.Skip(index).First().TriggerOnClick();
+ }
}
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index 7caab0dd6c..c40f517023 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -1,12 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Dialog;
-using OpenTK.Graphics;
-using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays
@@ -16,6 +13,20 @@ namespace osu.Game.Overlays
private readonly Container dialogContainer;
private PopupDialog currentDialog;
+ public DialogOverlay()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Child = dialogContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ Width = 0.4f;
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
public void Push(PopupDialog dialog)
{
if (dialog == currentDialog) return;
@@ -30,6 +41,10 @@ namespace osu.Game.Overlays
State = Visibility.Visible;
}
+ protected override bool PlaySamplesOnStateChange => false;
+
+ protected override bool BlockPassThroughKeyboard => true;
+
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
{
if (v != Visibility.Hidden) return;
@@ -50,32 +65,14 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
base.PopOut();
- this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
- }
- public DialogOverlay()
- {
- RelativeSizeAxes = Axes.Both;
-
- Children = new Drawable[]
+ if (currentDialog?.State == Visibility.Visible)
{
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black.Opacity(0.5f),
- },
- },
- },
- dialogContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- };
+ currentDialog.Hide();
+ return;
+ }
+
+ this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
}
}
}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 4f4348c131..df98cc3c32 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -35,15 +35,13 @@ namespace osu.Game.Overlays.Direct
}
[BackgroundDependencyLoader(true)]
- private void load(OsuGame game, RulesetStore rulesets, OsuColour colours)
+ private void load(RulesetStore rulesets, OsuColour colours, Bindable ruleset)
{
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
- Ruleset.Value = game?.Ruleset.Value ?? rulesets.GetRuleset(0);
+ Ruleset.Value = ruleset ?? rulesets.GetRuleset(0);
foreach (var r in rulesets.AvailableRulesets)
- {
modeButtons.Add(new RulesetToggleButton(Ruleset, r));
- }
}
private class RulesetToggleButton : OsuClickableContainer
diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs
index 4b91a3d700..28cc484109 100644
--- a/osu.Game/Overlays/Direct/PlayButton.cs
+++ b/osu.Game/Overlays/Direct/PlayButton.cs
@@ -10,6 +10,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
+using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct
@@ -48,9 +49,15 @@ namespace osu.Game.Overlays.Direct
set
{
if (value)
+ {
+ icon.FadeTo(0.5f, transition_duration, Easing.OutQuint);
loadingAnimation.Show();
+ }
else
+ {
+ icon.FadeTo(1, transition_duration, Easing.OutQuint);
loadingAnimation.Hide();
+ }
}
}
@@ -67,7 +74,10 @@ namespace osu.Game.Overlays.Direct
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_play,
},
- loadingAnimation = new LoadingAnimation(),
+ loadingAnimation = new LoadingAnimation
+ {
+ Size = new Vector2(15),
+ },
});
Playing.ValueChanged += playingStateChanged;
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index ffe1560627..5d4d627995 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -186,7 +186,7 @@ namespace osu.Game.Overlays.KeyBinding
{
if (bindTarget.IsHovered)
{
- bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
+ bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta));
finalise();
return true;
}
@@ -202,9 +202,6 @@ namespace osu.Game.Overlays.KeyBinding
switch (args.Key)
{
- case Key.Escape:
- finalise();
- return true;
case Key.Delete:
{
if (state.Keyboard.ShiftPressed)
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index f1624721da..c584a32a82 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods
public readonly Bindable> SelectedMods = new Bindable>();
- public readonly Bindable Ruleset = new Bindable();
+ public readonly IBindable Ruleset = new Bindable();
private void rulesetChanged(RulesetInfo newRuleset)
{
@@ -51,8 +51,8 @@ namespace osu.Game.Overlays.Mods
refreshSelectedMods();
}
- [BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio)
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, IBindable ruleset, AudioManager audio)
{
SelectedMods.ValueChanged += selectedModsChanged;
@@ -60,13 +60,8 @@ namespace osu.Game.Overlays.Mods
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
- if (osu != null)
- Ruleset.BindTo(osu.Ruleset);
- else
- Ruleset.Value = rulesets.AvailableRulesets.First();
-
- Ruleset.ValueChanged += rulesetChanged;
- Ruleset.TriggerChange();
+ Ruleset.BindTo(ruleset);
+ Ruleset.BindValueChanged(rulesetChanged, true);
sampleOn = audio.Sample.Get(@"UI/check-on");
sampleOff = audio.Sample.Get(@"UI/check-off");
diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs
new file mode 100644
index 0000000000..11ee329f33
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs
@@ -0,0 +1,20 @@
+// 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.Graphics;
+
+namespace osu.Game.Overlays.Profile.Components
+{
+ public class DrawableJoinDate : DrawableDate
+ {
+ public DrawableJoinDate(DateTimeOffset date)
+ : base(date)
+ {
+ }
+
+ protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}";
+
+ public override string TooltipText => $"{Date:MMMM d, yyyy}";
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs
new file mode 100644
index 0000000000..14a47e8d03
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Components/GradeBadge.cs
@@ -0,0 +1,50 @@
+// 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.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Profile.Components
+{
+ public class GradeBadge : Container
+ {
+ private const float width = 50;
+ private readonly string grade;
+ private readonly Sprite badge;
+ private readonly SpriteText numberText;
+
+ public int DisplayCount
+ {
+ set => numberText.Text = value.ToString(@"#,0");
+ }
+
+ public GradeBadge(string grade)
+ {
+ this.grade = grade;
+ Width = width;
+ Height = 41;
+ Add(badge = new Sprite
+ {
+ Width = width,
+ Height = 26
+ });
+ Add(numberText = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ TextSize = 14,
+ Font = @"Exo2.0-Bold"
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ badge.Texture = textures.Get($"Grades/{grade}");
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 067144c26e..c72ff6131b 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Profile.Components;
using osu.Game.Overlays.Profile.Header;
using osu.Game.Users;
@@ -375,12 +376,12 @@ namespace osu.Game.Overlays.Profile
if (user.JoinDate.ToUniversalTime().Year < 2008)
{
- infoTextLeft.AddText("Here since the beginning", boldItalic);
+ infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText);
}
else
{
infoTextLeft.AddText("Joined ", lightText);
- infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic);
+ infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic);
}
infoTextLeft.NewLine();
@@ -470,43 +471,5 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine();
}
-
- private class GradeBadge : Container
- {
- private const float width = 50;
- private readonly string grade;
- private readonly Sprite badge;
- private readonly SpriteText numberText;
-
- public int DisplayCount
- {
- set { numberText.Text = value.ToString(@"#,0"); }
- }
-
- public GradeBadge(string grade)
- {
- this.grade = grade;
- Width = width;
- Height = 41;
- Add(badge = new Sprite
- {
- Width = width,
- Height = 26
- });
- Add(numberText = new OsuSpriteText
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- TextSize = 14,
- Font = @"Exo2.0-Bold"
- });
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- badge.Texture = textures.Get($"Grades/{grade}");
- }
- }
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
index 046c1b1a33..fefb289d17 100644
--- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
+++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
@@ -141,11 +141,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
break;
case RecentActivityType.UserSupportFirst:
- message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!";
+ message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!";
break;
case RecentActivityType.UserSupportGift:
- message = $"{userLinkTemplate()} has received the gift of osu! supporter!";
+ message = $"{userLinkTemplate()} has received the gift of osu!supporter!";
break;
case RecentActivityType.UsernameChange:
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 930b3c1eaa..18a371e904 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -56,7 +56,13 @@ namespace osu.Game.Overlays.Settings.Sections
reloadSkins();
- skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin);
+ var skinBindable = config.GetBindable(OsuSetting.Skin);
+
+ // Todo: This should not be necessary when OsuConfigManager is databased
+ if (skinDropdown.Items.All(s => s.Value != skinBindable.Value))
+ skinBindable.Value = 0;
+
+ skinDropdown.Bindable = skinBindable;
}
private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID));
diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 3078c44844..dae4f84b1a 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -67,8 +67,8 @@ namespace osu.Game.Overlays.Toolbar
};
}
- [BackgroundDependencyLoader(true)]
- private void load(RulesetStore rulesets, OsuGame game)
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets, Bindable parentRuleset)
{
this.rulesets = rulesets;
foreach (var r in rulesets.AvailableRulesets)
@@ -82,11 +82,7 @@ namespace osu.Game.Overlays.Toolbar
ruleset.ValueChanged += rulesetChanged;
ruleset.DisabledChanged += disabledChanged;
-
- if (game != null)
- ruleset.BindTo(game.Ruleset);
- else
- ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
+ ruleset.BindTo(parentRuleset);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 572b3f0c27..3a64e12b27 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -9,11 +9,13 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume
{
- public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput
+ public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput
{
public Func ActionRequested;
+ public Func ScrollActionRequested;
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
+ public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
public bool OnReleased(GlobalAction action) => false;
}
}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 1d392e6ee8..9aeca1f35f 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -12,17 +12,15 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Volume
{
- public class VolumeMeter : Container, IKeyBindingHandler
+ public class VolumeMeter : Container
{
private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow;
@@ -226,59 +224,27 @@ namespace osu.Game.Overlays.Volume
private const float adjust_step = 0.05f;
- public void Increase() => adjust(1);
- public void Decrease() => adjust(-1);
-
- private void adjust(int direction)
- {
- float amount = adjust_step * direction;
-
- // handle the case where the OnPressed action was actually a mouse wheel.
- // this allows for precise wheel handling.
- var state = GetContainingInputManager().CurrentState;
- if (state.Mouse?.ScrollDelta.Y != 0)
- {
- OnScroll(state);
- return;
- }
-
- Volume += amount;
- }
-
- public bool OnPressed(GlobalAction action)
- {
- if (!IsHovered) return false;
-
- switch (action)
- {
- case GlobalAction.DecreaseVolume:
- Decrease();
- return true;
- case GlobalAction.IncreaseVolume:
- Increase();
- return true;
- }
-
- return false;
- }
+ public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
+ public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
- private double scrollAmount;
+ private double adjustAccumulator;
+
+ private void adjust(double delta, bool isPrecise)
+ {
+ adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1);
+ if (Math.Abs(adjustAccumulator) < Bindable.Precision)
+ return;
+ Volume += adjustAccumulator;
+ adjustAccumulator = 0;
+ }
protected override bool OnScroll(InputState state)
{
- scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1);
-
- if (Math.Abs(scrollAmount) < Bindable.Precision)
- return true;
-
- Volume += scrollAmount;
- scrollAmount = 0;
+ adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll);
return true;
}
- public bool OnReleased(GlobalAction action) => false;
-
private const float transition_length = 500;
protected override bool OnHover(InputState state)
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index 1c9c615bbb..e40597b2d4 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays
muteButton.Current.ValueChanged += _ => Show();
}
- public bool Adjust(GlobalAction action)
+ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
{
if (!IsLoaded) return false;
@@ -103,13 +103,13 @@ namespace osu.Game.Overlays
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Decrease();
+ volumeMeterMaster.Decrease(amount, isPrecise);
return true;
case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Increase();
+ volumeMeterMaster.Increase(amount, isPrecise);
return true;
case GlobalAction.ToggleMute:
Show();
diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs
index 587f2c8d15..129dd07c3e 100644
--- a/osu.Game/Rulesets/Judgements/Judgement.cs
+++ b/osu.Game/Rulesets/Judgements/Judgement.cs
@@ -45,11 +45,15 @@ namespace osu.Game.Rulesets.Judgements
public double TimeOffset { get; set; }
///
- /// Whether the should affect the combo portion of the score.
- /// If false, the will be considered for the bonus portion of the score.
+ /// Whether the should affect the current combo.
///
public virtual bool AffectsCombo => true;
+ ///
+ /// Whether the should be counted as base (combo) or bonus score.
+ ///
+ public virtual bool IsBonus => !AffectsCombo;
+
///
/// The numeric representation for the result achieved.
///
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 88990d435c..a22aaa784f 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual IEnumerable GetSamples() => HitObject.Samples;
private readonly Lazy> nestedHitObjects = new Lazy>();
- public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated;
- public IReadOnlyList NestedHitObjects => nestedHitObjects.Value;
+ public IEnumerable NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty();
public event Action OnJudgement;
public event Action OnJudgementRemoved;
@@ -50,12 +49,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Whether this and all of its nested s have been hit.
///
- public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit));
+ public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && NestedHitObjects.All(n => n.IsHit);
///
/// Whether this and all of its nested s have been judged.
///
- public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged));
+ public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && NestedHitObjects.All(h => h.AllJudged);
///
/// Whether this can be judged.
@@ -90,13 +89,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
- AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo
- {
- Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank,
- Name = s.Name,
- Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume,
- Namespace = SampleNamespace
- }).ToArray()));
+
+ samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
+ foreach (var s in samples)
+ s.Namespace = SampleNamespace;
+
+ AddInternal(Samples = new SkinnableSound(samples));
}
}
@@ -206,9 +204,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (AllJudged)
return false;
- if (HasNestedHitObjects)
- foreach (var d in NestedHitObjects)
- judgementOccurred |= d.UpdateJudgement(userTriggered);
+ foreach (var d in NestedHitObjects)
+ judgementOccurred |= d.UpdateJudgement(userTriggered);
if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
return judgementOccurred;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 9edd0f1f34..95589d8953 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
@@ -196,9 +197,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
- // Let's not implement this for now, because this doesn't fit nicely into the bank structure
- //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
-
string stringBank = bank.ToString().ToLower();
if (stringBank == @"none")
stringBank = null;
@@ -211,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 3)
bankInfo.Volume = int.Parse(split[3]);
+
+ bankInfo.Filename = split.Length > 4 ? split[4] : null;
}
///
@@ -252,6 +252,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{
+ // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
+ if (!string.IsNullOrEmpty(bankInfo.Filename))
+ return new List { new FileSampleInfo { Filename = bankInfo.Filename } };
+
var soundTypes = new List
{
new SampleInfo
@@ -297,14 +301,24 @@ namespace osu.Game.Rulesets.Objects.Legacy
private class SampleBankInfo
{
+ public string Filename;
+
public string Normal;
public string Add;
public int Volume;
- public SampleBankInfo Clone()
+ public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
+ }
+
+ private class FileSampleInfo : SampleInfo
+ {
+ public string Filename;
+
+ public override IEnumerable LookupNames => new[]
{
- return (SampleBankInfo)MemberwiseClone();
- }
+ Filename,
+ Path.ChangeExtension(Filename, null)
+ };
}
[Flags]
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
index 17848d1e6e..ef1eecec3d 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
@@ -10,7 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Objects.Legacy
{
- internal abstract class ConvertSlider : HitObject, IHasCurve
+ internal abstract class ConvertSlider : HitObject, IHasCurve, IHasLegacyLastTickOffset
{
///
/// Scoring distance with a speed-adjusted beat length of 1 second.
@@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
Velocity = scoringDistance / timingPoint.BeatLength;
}
+
+ public double LegacyLastTickOffset => 36;
}
}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs b/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs
deleted file mode 100644
index c5d0152ae7..0000000000
--- a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Rulesets.Objects.Types
-{
- ///
- /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
- ///
- public interface IHasComboIndex : IHasCombo
- {
- ///
- /// The offset of this hitobject in the current combo.
- ///
- int IndexInCurrentCombo { get; set; }
-
- ///
- /// The offset of this hitobject in the current combo.
- ///
- int ComboIndex { get; set; }
-
- ///
- /// Whether this is the last object in the current combo.
- ///
- bool LastInCombo { get; set; }
- }
-}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs b/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs
new file mode 100644
index 0000000000..ab2573e933
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs
@@ -0,0 +1,14 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Objects.Types
+{
+ ///
+ /// A type of which may require the last tick to be offset.
+ /// This is specific to osu!stable conversion, and should not be used elsewhere.
+ ///
+ public interface IHasLegacyLastTickOffset
+ {
+ double LegacyLastTickOffset { get; }
+ }
+}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index f818523a3d..82d9945ef7 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -57,8 +57,18 @@ namespace osu.Game.Rulesets
///
public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap);
+ ///
+ /// Creates a to convert a to one that is applicable for this .
+ ///
+ /// The to be converted.
+ /// The .
public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
+ ///
+ /// Optionally creates a to alter a after it has been converted.
+ ///
+ /// The to be processed.
+ /// The .
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 8d267f48e9..513173ef2f 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -84,10 +84,17 @@ namespace osu.Game.Rulesets
{
try
{
- var instance = r.CreateInstance();
+ var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo, asm =>
+ {
+ // for the time being, let's ignore the version being loaded.
+ // this allows for debug builds to successfully load rulesets (even though debug rulesets have a 0.0.0 version).
+ asm.Version = null;
+ return Assembly.Load(asm);
+ }, null), (RulesetInfo)null)).RulesetInfo;
- r.Name = instance.Description;
- r.ShortName = instance.ShortName;
+ r.Name = instanceInfo.Name;
+ r.ShortName = instanceInfo.ShortName;
+ r.InstantiationInfo = instanceInfo.InstantiationInfo;
r.Available = true;
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index dd4120f2fb..9e8ea0f7c2 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -261,13 +261,19 @@ namespace osu.Game.Rulesets.Scoring
break;
}
- baseScore += judgement.NumericResult;
- rollingMaxBaseScore += judgement.MaxNumericResult;
-
JudgedHits++;
}
- else if (judgement.IsHit)
- bonusScore += judgement.NumericResult;
+
+ if (judgement.IsBonus)
+ {
+ if (judgement.IsHit)
+ bonusScore += judgement.NumericResult;
+ }
+ else
+ {
+ baseScore += judgement.NumericResult;
+ rollingMaxBaseScore += judgement.MaxNumericResult;
+ }
}
///
@@ -280,14 +286,18 @@ namespace osu.Game.Rulesets.Scoring
HighestCombo.Value = judgement.HighestComboAtJudgement;
if (judgement.AffectsCombo)
+ JudgedHits--;
+
+ if (judgement.IsBonus)
+ {
+ if (judgement.IsHit)
+ bonusScore -= judgement.NumericResult;
+ }
+ else
{
baseScore -= judgement.NumericResult;
rollingMaxBaseScore -= judgement.MaxNumericResult;
-
- JudgedHits--;
}
- else if (judgement.IsHit)
- bonusScore -= judgement.NumericResult;
}
private void updateScore()
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index f8c4fff5b8..8046e9f9b4 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
{
- InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique);
+ InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique);
}
#region Action mapping (for replays)
@@ -267,6 +267,9 @@ namespace osu.Game.Rulesets.UI
}
#endregion
+
+ protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new RulesetKeyBindingContainer(ruleset, variant, unique);
}
///
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 36c6b07f54..c64fca6eff 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -29,17 +29,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
protected readonly SortedList ControlPoints = new SortedList();
- private readonly ScrollingDirection direction;
+ public readonly Bindable Direction = new Bindable();
private Cached initialStateCache = new Cached();
- public ScrollingHitObjectContainer(ScrollingDirection direction)
+ public ScrollingHitObjectContainer()
{
- this.direction = direction;
-
RelativeSizeAxes = Axes.Both;
- TimeRange.ValueChanged += v => initialStateCache.Invalidate();
+ TimeRange.ValueChanged += _ => initialStateCache.Invalidate();
+ Direction.ValueChanged += _ => initialStateCache.Invalidate();
}
private ISpeedChangeVisualiser speedChangeVisualiser;
@@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (!initialStateCache.IsValid)
{
- speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize);
+ speedChangeVisualiser.ComputeInitialStates(Objects, Direction, TimeRange, DrawSize);
initialStateCache.Validate();
}
}
@@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
base.UpdateAfterChildrenLife();
// We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions
- speedChangeVisualiser.UpdatePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize);
+ speedChangeVisualiser.UpdatePositions(AliveObjects, Direction, Time.Current, TimeRange, DrawSize);
}
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
index 830214803c..cfcfca157b 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
@@ -4,30 +4,33 @@
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
-using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
-using OpenTK.Input;
namespace osu.Game.Rulesets.UI.Scrolling
{
///
/// A type of specialized towards scrolling s.
///
- public abstract class ScrollingPlayfield : Playfield
+ public abstract class ScrollingPlayfield : Playfield, IKeyBindingHandler
{
///
/// The default span of time visible by the length of the scrolling axes.
/// This is clamped between and .
///
private const double time_span_default = 1500;
+
///
/// The minimum span of time that may be visible by the length of the scrolling axes.
///
private const double time_span_min = 50;
+
///
/// The maximum span of time that may be visible by the length of the scrolling axes.
///
private const double time_span_max = 10000;
+
///
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
///
@@ -54,7 +57,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects;
- private readonly ScrollingDirection direction;
+ protected readonly Bindable Direction = new Bindable();
///
/// Creates a new .
@@ -69,7 +72,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null)
: base(customWidth, customHeight)
{
- this.direction = direction;
+ Direction.Value = direction;
}
[BackgroundDependencyLoader]
@@ -78,27 +81,31 @@ namespace osu.Game.Rulesets.UI.Scrolling
HitObjects.TimeRange.BindTo(VisibleTimeRange);
}
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ public bool OnPressed(GlobalAction action)
{
if (!UserScrollSpeedAdjustment)
return false;
- if (state.Keyboard.ControlPressed)
+ switch (action)
{
- switch (args.Key)
- {
- case Key.Minus:
- this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 600, Easing.OutQuint);
- break;
- case Key.Plus:
- this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 600, Easing.OutQuint);
- break;
- }
+ case GlobalAction.IncreaseScrollSpeed:
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
+ return true;
+ case GlobalAction.DecreaseScrollSpeed:
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
+ return true;
}
return false;
}
- protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction);
+ public bool OnReleased(GlobalAction action) => false;
+
+ protected sealed override HitObjectContainer CreateHitObjectContainer()
+ {
+ var container = new ScrollingHitObjectContainer();
+ container.Direction.BindTo(Direction);
+ return container;
+ }
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
index 35a91275a7..d2b79e2fa7 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
@@ -47,13 +47,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
}
}
- if (obj.HasNestedHitObjects)
- {
- ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
+ ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
- // Nested hitobjects don't need to scroll, but they do need accurate positions
- UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
- }
+ // Nested hitobjects don't need to scroll, but they do need accurate positions
+ UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
index e353c07e9f..25dea8dfbf 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
@@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
}
}
- if (obj.HasNestedHitObjects)
- {
- ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
+ ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
- // Nested hitobjects don't need to scroll, but they do need accurate positions
- UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
- }
+ // Nested hitobjects don't need to scroll, but they do need accurate positions
+ UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
@@ -93,6 +90,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
/// A positive value indicating the position at .
private double positionAt(double time, double timeRange)
{
+ if (controlPoints.Count == 0)
+ return time / timeRange;
+
double length = 0;
// We need to consider all timing points until the specified time and not just the currently-active one,
diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs
index 5e9863f642..9d9432b2ce 100644
--- a/osu.Game/Screens/BackgroundScreen.cs
+++ b/osu.Game/Screens/BackgroundScreen.cs
@@ -40,7 +40,14 @@ namespace osu.Game.Screens
while (screen.LoadState < LoadState.Ready)
Thread.Sleep(1);
- base.Push(screen);
+ try
+ {
+ base.Push(screen);
+ }
+ catch (ScreenAlreadyExitedException)
+ {
+ // screen may have exited before the push was successful.
+ }
}
protected override void Update()
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
index d1fc8be005..0dc110951b 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
@@ -52,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
if (Beatmap.Value == null)
return;
- if (Beatmap.Value.Track.Length == double.PositiveInfinity) return;
-
float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length);
}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
index 07d9398d38..5628630d0e 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
@@ -47,8 +47,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return;
}
- // Todo: This should be handled more gracefully
- timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
+ timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
}
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
index e993d36551..8cb0fdd908 100644
--- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
@@ -52,17 +52,20 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform);
- waveform.Waveform = Beatmap.Value.Waveform;
+ Beatmap.BindValueChanged(b =>
+ {
+ waveform.Waveform = b.Waveform;
+ track = b.Track;
+ }, true);
}
///
- /// The track's time in the previous frame.
+ /// The timeline's scroll position in the last frame.
+ ///
+ private float lastScrollPosition;
+
+ ///
+ /// The track time in the last frame.
///
private double lastTrackTime;
@@ -76,6 +79,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
///
private bool trackWasPlaying;
+ private Track track;
+
protected override void Update()
{
base.Update();
@@ -83,49 +88,48 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
// The extrema of track time should be positioned at the centre of the container when scrolled to the start or end
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
- if (handlingDragInput)
- {
- // The user is dragging - the track should always follow the timeline
- seekTrackToCurrent();
- }
- else if (adjustableClock.IsRunning)
- {
- // If the user hasn't provided mouse input but the track is running, always follow the track
+ // This needs to happen after transforms are updated, but before the scroll position is updated in base.UpdateAfterChildren
+ if (adjustableClock.IsRunning)
scrollToTrackTime();
- }
- else
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ if (handlingDragInput)
+ seekTrackToCurrent();
+ else if (!adjustableClock.IsRunning)
{
- // The track isn't playing, so we want to smooth-scroll once more, and re-enable wheel scrolling
- // There are two cases we have to be wary of:
- // 1) The user scrolls on this timeline: We want the track to follow us
+ // The track isn't running. There are two cases we have to be wary of:
+ // 1) The user flick-drags on this timeline: We want the track to follow us
// 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time
- // The simplest way to cover both cases is by checking that inter-frame track times are identical
- if (adjustableClock.CurrentTime == lastTrackTime)
- {
- // The track hasn't been seeked externally
+ // The simplest way to cover both cases is by checking whether the scroll position has changed and the audio hasn't been changed externally
+ if (Current != lastScrollPosition && adjustableClock.CurrentTime == lastTrackTime)
seekTrackToCurrent();
- }
else
- {
- // The track has been seeked externally
scrollToTrackTime();
- }
}
+ lastScrollPosition = Current;
lastTrackTime = adjustableClock.CurrentTime;
+ }
- void seekTrackToCurrent()
- {
- if (!(Beatmap.Value.Track is TrackVirtual))
- adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
- }
+ private void seekTrackToCurrent()
+ {
+ if (!track.IsLoaded)
+ return;
- void scrollToTrackTime()
- {
- if (!(Beatmap.Value.Track is TrackVirtual))
- ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false);
- }
+ adjustableClock.Seek(Current / Content.DrawWidth * track.Length);
+ }
+
+ private void scrollToTrackTime()
+ {
+ if (!track.IsLoaded)
+ return;
+
+ ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 81abc4cd3d..374877673f 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -148,8 +148,6 @@ namespace osu.Game.Screens.Menu
case Key.Space:
logo?.TriggerOnClick(state);
return true;
- case Key.Escape:
- return goBack();
}
return false;
@@ -181,16 +179,7 @@ namespace osu.Game.Screens.Menu
}
}
- public bool OnReleased(GlobalAction action)
- {
- switch (action)
- {
- case GlobalAction.Back:
- return true;
- default:
- return false;
- }
- }
+ public bool OnReleased(GlobalAction action) => false;
private void onPlay()
{
diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
index 62605da5a4..4ada46749c 100644
--- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
+++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
@@ -1,34 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
-using OpenTK.Input;
namespace osu.Game.Screens.Menu
{
- public class ExitConfirmOverlay : HoldToConfirmOverlay
+ public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler
{
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ public bool OnPressed(GlobalAction action)
{
- if (args.Key == Key.Escape && !args.Repeat)
+ if (action == GlobalAction.Back)
{
BeginConfirm();
return true;
}
- return base.OnKeyDown(state, args);
+ return false;
}
- protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
+ public bool OnReleased(GlobalAction action)
{
- if (args.Key == Key.Escape)
+ if (action == GlobalAction.Back)
{
AbortConfirm();
return true;
}
- return base.OnKeyUp(state, args);
+ return false;
}
}
}
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 61018f9e08..2a7daff3d0 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -8,7 +8,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
-using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
@@ -17,7 +16,6 @@ using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
using OpenTK;
-using OpenTK.Input;
using osu.Game.Overlays;
using osu.Framework.Graphics.Containers;
@@ -82,22 +80,21 @@ namespace osu.Game.Screens
private SampleChannel sampleExit;
[BackgroundDependencyLoader(true)]
- private void load(BindableBeatmap beatmap, OsuGame osuGame, AudioManager audio)
+ private void load(BindableBeatmap beatmap, OsuGame osu, AudioManager audio, Bindable ruleset)
{
- if (beatmap != null)
- Beatmap.BindTo(beatmap);
+ Beatmap.BindTo(beatmap);
+ Ruleset.BindTo(ruleset);
- if (osuGame != null)
+ if (osu != null)
{
- Ruleset.BindTo(osuGame.Ruleset);
- OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
+ OverlayActivationMode.BindTo(osu.OverlayActivationMode);
updateOverlayStates = () =>
{
if (HideOverlaysOnEnter)
- osuGame.CloseAllOverlays();
+ osu.CloseAllOverlays();
else
- osuGame.Toolbar.State = Visibility.Visible;
+ osu.Toolbar.State = Visibility.Visible;
};
}
@@ -106,6 +103,8 @@ namespace osu.Game.Screens
public bool OnPressed(GlobalAction action)
{
+ if (!IsCurrentScreen) return false;
+
if (action == GlobalAction.Back && AllowBackButton)
{
Exit();
@@ -117,20 +116,6 @@ namespace osu.Game.Screens
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
- {
- if (args.Repeat || !IsCurrentScreen) return false;
-
- switch (args.Key)
- {
- case Key.Escape:
- Exit();
- return true;
- }
-
- return base.OnKeyDown(state, args);
- }
-
protected override void OnResuming(Screen last)
{
sampleExit?.Play();
@@ -195,11 +180,10 @@ namespace osu.Game.Screens
if (Background != null && !Background.Equals(nextOsu?.Background))
{
- if (nextOsu != null)
- //We need to use MakeCurrent in case we are jumping up multiple game screens.
- nextOsu.Background?.MakeCurrent();
- else
- Background.Exit();
+ Background.Exit();
+
+ //We need to use MakeCurrent in case we are jumping up multiple game screens.
+ nextOsu?.Background?.MakeCurrent();
}
if (base.OnExiting(next))
diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs
index 7b555776f7..bbed0c8843 100644
--- a/osu.Game/Screens/Play/FailOverlay.cs
+++ b/osu.Game/Screens/Play/FailOverlay.cs
@@ -1,12 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Input;
-using OpenTK.Input;
using osu.Game.Graphics;
using OpenTK.Graphics;
using osu.Framework.Allocation;
-using System.Linq;
namespace osu.Game.Screens.Play
{
@@ -21,16 +18,5 @@ namespace osu.Game.Screens.Play
AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke());
AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
}
-
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
- {
- if (!args.Repeat && args.Key == Key.Escape)
- {
- InternalButtons.Children.Last().TriggerOnClick();
- return true;
- }
-
- return base.OnKeyDown(state, args);
- }
}
}
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index db099cd16c..52c08bb351 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -15,10 +15,13 @@ using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Shapes;
using OpenTK.Input;
using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Play
{
- public abstract class GameplayMenuOverlay : OverlayContainer
+ public abstract class GameplayMenuOverlay : OverlayContainer, IKeyBindingHandler
{
private const int transition_duration = 200;
private const int button_height = 70;
@@ -31,6 +34,11 @@ namespace osu.Game.Screens.Play
public Action OnRetry;
public Action OnQuit;
+ ///
+ /// Action that is invoked when is triggered.
+ ///
+ protected virtual Action BackAction => () => InternalButtons.Children.Last().TriggerOnClick();
+
public abstract string Header { get; }
public abstract string Description { get; }
@@ -219,6 +227,19 @@ namespace osu.Game.Screens.Play
return base.OnKeyDown(state, args);
}
+ public bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ BackAction.Invoke();
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
+
private void buttonSelectionChanged(DialogButton button, bool isSelected)
{
if (!isSelected)
diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs
index 6262f71ddc..e2f133dde3 100644
--- a/osu.Game/Screens/Play/PauseContainer.cs
+++ b/osu.Game/Screens/Play/PauseContainer.cs
@@ -6,11 +6,9 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Graphics;
using OpenTK.Graphics;
-using OpenTK.Input;
namespace osu.Game.Screens.Play
{
@@ -138,16 +136,7 @@ namespace osu.Game.Screens.Play
public override string Header => "paused";
public override string Description => "you're not going to do what i think you're going to do, are ya?";
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
- {
- if (!args.Repeat && args.Key == Key.Escape)
- {
- InternalButtons.Children.First().TriggerOnClick();
- return true;
- }
-
- return base.OnKeyDown(state, args);
- }
+ protected override Action BackAction => () => InternalButtons.Children.First().TriggerOnClick();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index a2ed01f5a7..b406bda411 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -49,8 +49,6 @@ namespace osu.Game.Screens.Play
public bool AllowLeadIn { get; set; } = true;
public bool AllowResults { get; set; } = true;
- protected override bool AllowBackButton => false;
-
private Bindable mouseWheelDisabled;
private Bindable userAudioOffset;
@@ -158,7 +156,8 @@ namespace osu.Game.Screens.Play
userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
- config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
+ if (!ScoreProcessor.Mode.Disabled)
+ config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
Children = new Drawable[]
{
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 7950018554..d26702fcf9 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -53,10 +53,9 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader(true)]
- private void load([CanBeNull] OsuGame osuGame)
+ private void load([CanBeNull] Bindable parentRuleset)
{
- if (osuGame != null)
- ruleset.BindTo(osuGame.Ruleset);
+ ruleset.BindTo(parentRuleset);
ruleset.ValueChanged += _ => updateDisplay();
}
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index f9f3db3827..278d32b2d5 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -29,6 +29,7 @@ namespace osu.Game.Screens.Select
private readonly TabControl groupTabs;
private SortMode sort = SortMode.Title;
+
public SortMode Sort
{
get { return sort; }
@@ -43,6 +44,7 @@ namespace osu.Game.Screens.Select
}
private GroupMode group = GroupMode.All;
+
public GroupMode Group
{
get { return group; }
@@ -62,14 +64,15 @@ namespace osu.Game.Screens.Select
Sort = sort,
SearchText = searchTextBox.Text,
AllowConvertedBeatmaps = showConverted,
- Ruleset = ruleset
+ Ruleset = ruleset.Value
};
public Action Exit;
private readonly SearchTextBox searchTextBox;
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || groupTabs.ReceiveMouseInputAt(screenSpacePos) || sortTabs.ReceiveMouseInputAt(screenSpacePos);
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) =>
+ base.ReceiveMouseInputAt(screenSpacePos) || groupTabs.ReceiveMouseInputAt(screenSpacePos) || sortTabs.ReceiveMouseInputAt(screenSpacePos);
public FilterControl()
{
@@ -163,24 +166,22 @@ namespace osu.Game.Screens.Select
searchTextBox.HoldFocus = true;
}
- private readonly Bindable ruleset = new Bindable();
+ private readonly IBindable ruleset = new Bindable();
private Bindable showConverted;
public readonly Box Background;
[BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuColour colours, OsuGame osu, OsuConfigManager config)
+ private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config)
{
sortTabs.AccentColour = colours.GreenLight;
showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps);
showConverted.ValueChanged += val => updateCriteria();
- if (osu != null)
- ruleset.BindTo(osu.Ruleset);
- ruleset.ValueChanged += val => updateCriteria();
- ruleset.TriggerChange();
+ ruleset.BindTo(parentRuleset);
+ ruleset.BindValueChanged(val => updateCriteria(), true);
}
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs
index 602b03380c..3fa12283b5 100644
--- a/osu.Game/Screens/Select/FooterButton.cs
+++ b/osu.Game/Screens/Select/FooterButton.cs
@@ -55,6 +55,8 @@ namespace osu.Game.Screens.Select
private readonly Box box;
private readonly Box light;
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos);
+
public FooterButton()
{
Children = new Drawable[]
diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
index 9dae8fb273..c171e791d2 100644
--- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
@@ -19,7 +19,6 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using System.Linq;
using osu.Framework.Configuration;
-using osu.Framework.Logging;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Select.Leaderboards
@@ -33,7 +32,7 @@ namespace osu.Game.Screens.Select.Leaderboards
private FillFlowContainer scrollFlow;
- private readonly Bindable ruleset = new Bindable();
+ private readonly IBindable ruleset = new Bindable();
public Action ScoreSelected;
@@ -41,7 +40,10 @@ namespace osu.Game.Screens.Select.Leaderboards
private ScheduledDelegate showScoresDelegate;
+ private bool scoresLoadedOnce;
+
private IEnumerable scores;
+
public IEnumerable Scores
{
get { return scores; }
@@ -49,6 +51,8 @@ namespace osu.Game.Screens.Select.Leaderboards
{
scores = value;
+ scoresLoadedOnce = true;
+
scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire();
scrollFlow = null;
@@ -146,7 +150,7 @@ namespace osu.Game.Screens.Select.Leaderboards
replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!"));
break;
case PlaceholderState.NotSupporter:
- replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!"));
+ replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"));
break;
default:
replacePlaceholder(null);
@@ -174,9 +178,8 @@ namespace osu.Game.Screens.Select.Leaderboards
private APIAccess api;
private BeatmapInfo beatmap;
- private OsuGame osuGame;
- private ScheduledDelegate pendingBeatmapSwitch;
+ private ScheduledDelegate pendingUpdateScores;
public BeatmapInfo Beatmap
{
@@ -189,21 +192,17 @@ namespace osu.Game.Screens.Select.Leaderboards
beatmap = value;
Scores = null;
- pendingBeatmapSwitch?.Cancel();
- pendingBeatmapSwitch = Schedule(updateScores);
+ updateScores();
}
}
[BackgroundDependencyLoader(permitNulls: true)]
- private void load(APIAccess api, OsuGame osuGame)
+ private void load(APIAccess api, IBindable parentRuleset)
{
this.api = api;
- this.osuGame = osuGame;
- if (osuGame != null)
- ruleset.BindTo(osuGame.Ruleset);
-
- ruleset.ValueChanged += r => updateScores();
+ ruleset.BindTo(parentRuleset);
+ ruleset.ValueChanged += _ => updateScores();
if (api != null)
api.OnStateChange += handleApiStateChange;
@@ -231,51 +230,61 @@ namespace osu.Game.Screens.Select.Leaderboards
private void updateScores()
{
- if (Scope == LeaderboardScope.Local)
- {
- // TODO: get local scores from wherever here.
- PlaceholderState = PlaceholderState.NoScores;
- return;
- }
+ // don't display any scores or placeholder until the first Scores_Set has been called.
+ // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished.
+ if (!scoresLoadedOnce) return;
- if (Beatmap?.OnlineBeatmapID == null)
- {
- PlaceholderState = PlaceholderState.Unavailable;
- return;
- }
+ getScoresRequest?.Cancel();
+ getScoresRequest = null;
- if (api?.IsLoggedIn != true)
+ pendingUpdateScores?.Cancel();
+ pendingUpdateScores = Schedule(() =>
{
- PlaceholderState = PlaceholderState.NotLoggedIn;
- return;
- }
-
- if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter)
- {
- PlaceholderState = PlaceholderState.NotSupporter;
- return;
- }
-
- PlaceholderState = PlaceholderState.Retrieving;
- loading.Show();
-
- getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope);
- getScoresRequest.Success += r => Schedule(() =>
- {
- Scores = r.Scores;
- PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
- });
-
- getScoresRequest.Failure += e => Schedule(() =>
- {
- if (e is OperationCanceledException)
+ if (Scope == LeaderboardScope.Local)
+ {
+ // TODO: get local scores from wherever here.
+ PlaceholderState = PlaceholderState.NoScores;
return;
+ }
- PlaceholderState = PlaceholderState.NetworkFailure;
- Logger.Error(e, @"Couldn't fetch beatmap scores!");
+ if (Beatmap?.OnlineBeatmapID == null)
+ {
+ PlaceholderState = PlaceholderState.Unavailable;
+ return;
+ }
+
+ if (api?.IsLoggedIn != true)
+ {
+ PlaceholderState = PlaceholderState.NotLoggedIn;
+ return;
+ }
+
+ if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter)
+ {
+ PlaceholderState = PlaceholderState.NotSupporter;
+ return;
+ }
+
+ PlaceholderState = PlaceholderState.Retrieving;
+ loading.Show();
+
+ getScoresRequest = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope);
+ getScoresRequest.Success += r => Schedule(() =>
+ {
+ Scores = r.Scores;
+ PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
+ });
+
+ getScoresRequest.Failure += e => Schedule(() =>
+ {
+ if (e is OperationCanceledException)
+ return;
+
+ PlaceholderState = PlaceholderState.NetworkFailure;
+ });
+
+ api.Queue(getScoresRequest);
});
-
- api.Queue(getScoresRequest);
}
private Placeholder currentPlaceholder;
@@ -331,23 +340,4 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
}
-
- public enum LeaderboardScope
- {
- Local,
- Country,
- Global,
- Friend,
- }
-
- public enum PlaceholderState
- {
- Successful,
- Retrieving,
- NetworkFailure,
- Unavailable,
- NoScores,
- NotLoggedIn,
- NotSupporter,
- }
}
diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs
new file mode 100644
index 0000000000..761f53a5e8
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+ public enum LeaderboardScope
+ {
+ Local,
+ Country,
+ Global,
+ Friend,
+ }
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
index ac2ac818d8..f01a55b662 100644
--- a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
+++ b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
@@ -2,10 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards
{
@@ -15,22 +12,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public MessagePlaceholder(string message)
{
- Direction = FillDirection.Horizontal;
- AutoSizeAxes = Axes.Both;
- Children = new Drawable[]
+ AddIcon(FontAwesome.fa_exclamation_circle, cp =>
{
- new SpriteIcon
- {
- Icon = FontAwesome.fa_exclamation_circle,
- Size = new Vector2(26),
- Margin = new MarginPadding { Right = 10 },
- },
- new OsuSpriteText
- {
- Text = this.message = message,
- TextSize = 22,
- },
- };
+ cp.TextSize = TEXT_SIZE;
+ cp.Padding = new MarginPadding { Right = 10 };
+ });
+
+ AddText(this.message = message);
}
public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;
diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs
index c56d279e6c..307986a299 100644
--- a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs
+++ b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs
@@ -3,16 +3,27 @@
using System;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Select.Leaderboards
{
- public abstract class Placeholder : FillFlowContainer, IEquatable
+ public abstract class Placeholder : OsuTextFlowContainer, IEquatable
{
+ protected const float TEXT_SIZE = 22;
+
+ public override bool HandleMouseInput => true;
+
protected Placeholder()
+ : base(cp => cp.TextSize = TEXT_SIZE)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
+ TextAnchor = Anchor.TopCentre;
+
+ Padding = new MarginPadding(20);
+
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
}
public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();
diff --git a/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs b/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs
new file mode 100644
index 0000000000..33a56540f3
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+ public enum PlaceholderState
+ {
+ Successful,
+ Retrieving,
+ NetworkFailure,
+ Unavailable,
+ NoScores,
+ NotLoggedIn,
+ NotSupporter,
+ }
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs
index 174fbac120..99b0c53835 100644
--- a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs
+++ b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs
@@ -3,11 +3,9 @@
using System;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards
@@ -18,22 +16,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetrievalFailurePlaceholder()
{
- Direction = FillDirection.Horizontal;
- AutoSizeAxes = Axes.Both;
- Children = new Drawable[]
+ AddArbitraryDrawable(new RetryButton
{
- new RetryButton
- {
- Action = () => OnRetry?.Invoke(),
- Margin = new MarginPadding { Right = 10 },
- },
- new OsuSpriteText
- {
- Anchor = Anchor.TopLeft,
- Text = @"Couldn't retrieve scores!",
- TextSize = 22,
- },
- };
+ Action = () => OnRetry?.Invoke(),
+ Padding = new MarginPadding { Right = 10 }
+ });
+
+ AddText(@"Couldn't retrieve scores!");
}
public class RetryButton : OsuHoverContainer
@@ -44,18 +33,16 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetryButton()
{
- Height = 26;
- Width = 26;
+ AutoSizeAxes = Axes.Both;
+
Child = new OsuClickableContainer
{
AutoSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
Action = () => Action?.Invoke(),
Child = icon = new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
- Size = new Vector2(26),
+ Size = new Vector2(TEXT_SIZE),
Shadow = true,
},
};
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 70b473bcd9..94c16f1797 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@@ -17,6 +18,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Menu;
@@ -62,6 +64,8 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap;
+ protected new readonly Bindable Ruleset = new Bindable();
+
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
@@ -123,7 +127,7 @@ namespace osu.Game.Screens.Select
Size = new Vector2(carousel_width, 1),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- SelectionChanged = carouselSelectionChanged,
+ SelectionChanged = updateSelectedBeatmap,
BeatmapSetsChanged = carouselBeatmapsLoaded,
},
FilterControl = new FilterControl
@@ -177,9 +181,13 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader(true)]
- private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours)
+ private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours)
{
dependencies.CacheAs(this);
+ dependencies.CacheAs(Ruleset);
+ dependencies.CacheAs>(Ruleset);
+
+ base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce);
if (Footer != null)
{
@@ -192,9 +200,6 @@ namespace osu.Game.Screens.Select
if (this.beatmaps == null)
this.beatmaps = beatmaps;
- if (osu != null)
- Ruleset.BindTo(osu.Ruleset);
-
this.beatmaps.ItemAdded += onBeatmapSetAdded;
this.beatmaps.ItemRemoved += onBeatmapSetRemoved;
this.beatmaps.BeatmapHidden += onBeatmapHidden;
@@ -250,9 +255,6 @@ namespace osu.Game.Screens.Select
private ScheduledDelegate selectionChangedDebounce;
- // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
- private BeatmapInfo beatmapNoDebounce;
-
private void workingBeatmapChanged(WorkingBeatmap beatmap)
{
if (beatmap is DummyWorkingBeatmap) return;
@@ -266,11 +268,17 @@ namespace osu.Game.Screens.Select
}
}
+ // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
+ private BeatmapInfo beatmapNoDebounce;
+ private RulesetInfo rulesetNoDebounce;
+
///
- /// selection has been changed as the result of interaction with the carousel.
+ /// selection has been changed as the result of a user interaction.
///
- private void carouselSelectionChanged(BeatmapInfo beatmap)
+ private void updateSelectedBeatmap(BeatmapInfo beatmap)
{
+ var ruleset = base.Ruleset.Value;
+
void performLoad()
{
// We may be arriving here due to another component changing the bindable Beatmap.
@@ -283,15 +291,18 @@ namespace osu.Game.Screens.Select
ensurePlayingSelected(preview);
}
+ Ruleset.Value = ruleset;
+
UpdateBeatmap(Beatmap.Value);
}
- if (beatmap?.Equals(beatmapNoDebounce) == true)
+ if (beatmap?.Equals(beatmapNoDebounce) == true && ruleset?.Equals(rulesetNoDebounce) == true)
return;
selectionChangedDebounce?.Cancel();
beatmapNoDebounce = beatmap;
+ rulesetNoDebounce = ruleset;
if (beatmap == null)
performLoad();
@@ -460,7 +471,7 @@ namespace osu.Game.Screens.Select
{
// in the case random selection failed, we want to trigger selectionChanged
// to show the dummy beatmap (we have nothing else to display).
- carouselSelectionChanged(null);
+ updateSelectedBeatmap(null);
}
}
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 1a11b0c03e..2fc9cff463 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -44,19 +44,17 @@ namespace osu.Game.Skinning
private SampleChannel loadChannel(SampleInfo info, Func getSampleFunction)
{
- SampleChannel ch = null;
+ foreach (var lookup in info.LookupNames)
+ {
+ var ch = getSampleFunction($"Gameplay/{lookup}");
+ if (ch == null)
+ continue;
- if (info.Namespace != null)
- ch = getSampleFunction($"Gameplay/{info.Namespace}/{info.Bank}-{info.Name}");
-
- // try without namespace as a fallback.
- if (ch == null)
- ch = getSampleFunction($"Gameplay/{info.Bank}-{info.Name}");
-
- if (ch != null)
ch.Volume.Value = info.Volume / 100.0;
+ return ch;
+ }
- return ch;
+ return null;
}
}
}
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 37693c99e8..2f5c887726 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -1,12 +1,10 @@
// 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.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Tests.Beatmaps
{
@@ -26,21 +24,6 @@ namespace osu.Game.Tests.Beatmaps
private readonly IBeatmap beatmap;
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
-
- protected override Track GetTrack()
- {
- var lastObject = beatmap.HitObjects.LastOrDefault();
- if (lastObject != null)
- return new TestTrack(((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime) + 1000);
- return new TrackVirtual();
- }
-
- private class TestTrack : TrackVirtual
- {
- public TestTrack(double length)
- {
- Length = length;
- }
- }
+ protected override Track GetTrack() => null;
}
}
diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs
index 1740658da6..5f70055021 100644
--- a/osu.Game/Tests/Visual/OsuTestCase.cs
+++ b/osu.Game/Tests/Visual/OsuTestCase.cs
@@ -1,10 +1,13 @@
// 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.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Configuration;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual
{
@@ -13,6 +16,8 @@ namespace osu.Game.Tests.Visual
private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap());
protected BindableBeatmap Beatmap => beatmap;
+ protected readonly Bindable Ruleset = new Bindable();
+
protected DependencyContainer Dependencies { get; private set; }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
@@ -22,13 +27,18 @@ namespace osu.Game.Tests.Visual
Dependencies.CacheAs(beatmap);
Dependencies.CacheAs(beatmap);
+ Dependencies.CacheAs(Ruleset);
+ Dependencies.CacheAs>(Ruleset);
+
return Dependencies;
}
[BackgroundDependencyLoader]
- private void load(AudioManager audioManager)
+ private void load(AudioManager audioManager, RulesetStore rulesets)
{
beatmap.SetAudioManager(audioManager);
+
+ Ruleset.Value = rulesets.AvailableRulesets.First();
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index 20c9646aa3..9afb1dd6cd 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -86,7 +86,11 @@ namespace osu.Game.Tests.Visual
private readonly WeakList workingWeakReferences = new WeakList();
private readonly WeakList playerWeakReferences = new WeakList();
- private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
+ private Player loadPlayerFor(RulesetInfo ri)
+ {
+ Ruleset.Value = ri;
+ return loadPlayerFor(ri.CreateInstance());
+ }
private Player loadPlayerFor(Ruleset r)
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 9cc538f70f..da55726447 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -14,12 +14,12 @@
-
-
-
+
+
+
-
-
+
+
diff --git a/osu.TestProject.props b/osu.TestProject.props
index 8f7128f8b7..c2e6048a60 100644
--- a/osu.TestProject.props
+++ b/osu.TestProject.props
@@ -11,7 +11,7 @@
-
+