diff --git a/osu-resources b/osu-resources index 92ec3d10b1..7bb0782200 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d +Subproject commit 7bb0782200abadf73b79ed1a3bc1d5b926c6a81e diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index d3012b1981..0cdc1694f4 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { public override void PostProcess(Beatmap beatmap) { - if (beatmap.ComboColors.Count == 0) + if (beatmap.ComboColours.Count == 0) return; int index = 0; @@ -31,11 +31,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (obj.NewCombo) { if (lastObj != null) lastObj.LastInCombo = true; - colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count; } obj.IndexInBeatmap = index++; - obj.ComboColour = beatmap.ComboColors[colourIndex]; + obj.ComboColour = beatmap.ComboColours[colourIndex]; lastObj = obj; } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs index 124af06d56..8eb8fd8435 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDaycore : ModDaycore { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index 5c025bdea0..07bc8b825a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModEasy : ModEasy { + public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs index 303fa6011d..947990cce5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModHalfTime : ModHalfTime { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index ed33bf7124..9479c9d9b0 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModHardRock : ModHardRock { public override double ScoreMultiplier => 1.12; - public override bool Ranked => true; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 981ebda9eb..14291f744c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModHidden : ModHidden { - public override string Description => @"Play with fading notes for a slight score advantage."; + public override string Description => @"Play with fading fruits."; public override double ScoreMultiplier => 1.06; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 7b0370ef88..3c6ec0703d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Origin = Anchor.BottomLeft; X = 0; - Child = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; foreach (var b in s.NestedHitObjects.Cast()) AddNested(getVisualRepresentation?.Invoke(b)); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index c2b0552ab3..f05f51052d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable [BackgroundDependencyLoader] private void load() { - Child = new Pulp + InternalChild = new Pulp { AccentColour = AccentColour, Size = Size diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 93a1483f6f..dcad82130e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable [BackgroundDependencyLoader] private void load() { - Children = new[] + InternalChildren = new[] { createPulp(HitObject.VisualRepresentation), border = new Circle @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable if (HitObject.HyperDash) { - Add(new Pulp + AddInternal(new Pulp { RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 965ca62674..0a2763cbea 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Origin = Anchor.BottomLeft; X = 0; - Child = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; + InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; foreach (var o in s.NestedHitObjects.Cast()) AddNested(getVisualRepresentation?.Invoke(o)); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a3e5aba2db..e4d5ae698f 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -65,54 +65,59 @@ namespace osu.Game.Rulesets.Catch.Objects X = X }); - for (var span = 0; span < this.SpanCount(); span++) + double lastDropletTime = StartTime; + + for (int span = 0; span < this.SpanCount(); span++) { var spanStartTime = StartTime + span * spanDuration; var reversed = span % 2 == 1; - for (var d = tickDistance; d <= length; d += tickDistance) + for (double d = 0; d <= length; d += tickDistance) { - if (d > length - minDistanceFromEnd) - break; - var timeProgress = d / length; var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - var lastTickTime = spanStartTime + timeProgress * spanDuration; - AddNested(new Droplet + double time = spanStartTime + timeProgress * spanDuration; + + double tinyTickInterval = time - lastDropletTime; + while (tinyTickInterval > 100) + tinyTickInterval /= 2; + + for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval) { - StartTime = lastTickTime, - ComboColour = ComboColour, - X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo + double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; + + AddNested(new TinyDroplet { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } + StartTime = t, + ComboColour = ComboColour, + X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } - double tinyTickInterval = tickDistance / length * spanDuration; - while (tinyTickInterval > 100) - tinyTickInterval /= 2; - - for (double t = 0; t < spanDuration; t += tinyTickInterval) - { - double progress = reversed ? 1 - t / spanDuration : t / spanDuration; - - AddNested(new TinyDroplet + if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd) { - StartTime = spanStartTime + t, - ComboColour = ComboColour, - X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo + AddNested(new Droplet { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); + StartTime = time, + ComboColour = ComboColour, + X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + lastDropletTime = time; } AddNested(new Fruit diff --git a/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs index 826c900140..e40510b71b 100644 --- a/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2149")] + [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] public new void Test(string name) { base.Test(name); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index 7c7dc5e4f7..99f49e6620 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModDaycore : ModDaycore { - public override double ScoreMultiplier => 0.3; + public override double ScoreMultiplier => 0.5; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index 64ce86e748..a9d77988c8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.0; + public override double ScoreMultiplier => 1; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index 3330d87e88..a1f9e0290e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Dual Stages"; public override string ShortenedName => "DS"; public override string Description => @"Double the stages, double the fun!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => false; + public override double ScoreMultiplier => 0; public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index 1faed5e1c0..0b3e851c64 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModEasy : ModEasy { + public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 03442507d6..ca5667a400 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -9,10 +9,11 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModFadeIn : Mod { - public override string Name => "FadeIn"; + public override string Name => "Fade In"; public override string ShortenedName => "FI"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; + public override string Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 89eb02268e..8d8693d11f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModFlashlight : ModFlashlight { - public override double ScoreMultiplier => 1.0; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index 2f8404609f..c00bb4275a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModHalfTime : ModHalfTime { - public override double ScoreMultiplier => 0.3; + public override double ScoreMultiplier => 0.5; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index 91edbaf0cf..8b77ea4c25 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModHardRock : ModHardRock { - public override double ScoreMultiplier => 1.0; + public override double ScoreMultiplier => 1; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index c2fc07da89..9317dba19f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModHidden : ModHidden { - public override string Description => @"The notes fade out before you hit them!"; - public override double ScoreMultiplier => 1.0; + public override string Description => @"Keys fade out before you hit them!"; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 8a6943d99b..c0107e3758 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey1 : ManiaKeyMod { public override int KeyCount => 1; - public override string Name => "1K"; + public override string Name => "One Key"; + public override string ShortenedName => "1K"; + public override string Description => @"Play with one key."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 553827ac1c..11dbe0ba76 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey2 : ManiaKeyMod { public override int KeyCount => 2; - public override string Name => "2K"; + public override string Name => "Two Keys"; + public override string ShortenedName => "2K"; + public override string Description => @"Play with two keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index ef048c848e..94ad53d8ea 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey3 : ManiaKeyMod { public override int KeyCount => 3; - public override string Name => "3K"; + public override string Name => "Three Keys"; + public override string ShortenedName => "3K"; + public override string Description => @"Play with three keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 9c713d920f..d9c27c5ef1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey4 : ManiaKeyMod { public override int KeyCount => 4; - public override string Name => "4K"; + public override string Name => "Four Keys"; + public override string ShortenedName => "4K"; + public override string Description => @"Play with four keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index a83faf4627..e54bae93a7 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey5 : ManiaKeyMod { public override int KeyCount => 5; - public override string Name => "5K"; + public override string Name => "Five Keys"; + public override string ShortenedName => "5K"; + public override string Description => @"Play with five keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index d7df901048..9c3bdf46b9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey6 : ManiaKeyMod { public override int KeyCount => 6; - public override string Name => "6K"; + public override string Name => "Six Keys"; + public override string ShortenedName => "6K"; + public override string Description => @"Play with six keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index 4a3f9857e5..f17ac80be5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey7 : ManiaKeyMod { public override int KeyCount => 7; - public override string Name => "7K"; + public override string Name => "Seven Keys"; + public override string ShortenedName => "7K"; + public override string Description => @"Play with seven keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index 22c301fb7a..36a6fc838f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey8 : ManiaKeyMod { public override int KeyCount => 8; - public override string Name => "8K"; + public override string Name => "Eight Keys"; + public override string ShortenedName => "8K"; + public override string Description => @"Play with eight keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index b2a0bc4ddf..10f03e2480 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKey9 : ManiaKeyMod { public override int KeyCount => 9; - public override string Name => "9K"; + public override string Name => "Nine Keys"; + public override string ShortenedName => "9K"; + public override string Description => @"Play with nine keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index a977eef5e3..a007224b74 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.0; + public override double ScoreMultiplier => 1; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index a6cbad44d7..df0f9a5437 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Random"; public override string ShortenedName => "RD"; public override FontAwesome Icon => FontAwesome.fa_osu_dice; - public override string Description => @"Shuffle around the notes!"; - public override double ScoreMultiplier => 1; + public override string Description => @"Shuffle around the keys!"; + public override double ScoreMultiplier => 0; public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 91c83a62f0..83d67c855e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Height = 1; - Add(new Box + AddInternal(new Box { Name = "Bar line", Anchor = Anchor.BottomCentre, @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (isMajor) { - Add(new EquilateralTriangle + AddInternal(new EquilateralTriangle { Name = "Left triangle", Anchor = Anchor.BottomLeft, @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Rotation = 90 }); - Add(new EquilateralTriangle + AddInternal(new EquilateralTriangle { Name = "Right triangle", Anchor = Anchor.BottomRight, diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 5a9ff592bc..6eb34c7005 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRange(new Drawable[] + InternalChildren = new Drawable[] { // The hit object itself cannot be used for various elements because the tail overshoots it // So a specialized container that is updated to contain the tail height is used @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } - }); + }; foreach (var tick in HitObject.NestedHitObjects.OfType()) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f9c0b96d37..b50a5e897e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Size = new Vector2(1); - Children = new[] + InternalChildren = new[] { glowContainer = new CircularContainer { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 8944978bdd..c8aa4588a8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Children = new Drawable[] + InternalChildren = new Drawable[] { laneGlowPiece = new LaneGlowPiece { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 3dad5b508c..bfcdec9321 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { applyStacking(beatmap); - if (beatmap.ComboColors.Count == 0) + if (beatmap.ComboColours.Count == 0) return; int comboIndex = 0; @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (obj.NewCombo) { comboIndex = 0; - colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count; } obj.IndexInCurrentCombo = comboIndex++; - obj.ComboColour = beatmap.ComboColors[colourIndex]; + obj.ComboColour = beatmap.ComboColours[colourIndex]; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs index eb90338e2f..987bb28932 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDaycore : ModDaycore { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index 80c83bf5d8..d842b607c6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModEasy : ModEasy { + public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs index 7d009b0344..1b9291bcf3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModHalfTime : ModHalfTime { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 29bf3e248d..74c3585d3d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModHardRock : ModHardRock, IApplicableToHitObject { public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; public void ApplyToHitObject(OsuHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4aeb76121a..1117b5bbd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects { - public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; + public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; private const double fade_in_duration_multiplier = 0.4; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 057916c04b..c9def8c8cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModRelax : ModRelax { - public override string Description => "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things."; + public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 18b212f781..401e56a3c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Spun Out"; public override string ShortenedName => "SO"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; - public override string Description => @"Spinners will be automatically completed"; + public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b2b5130be3..613fbc4e32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Target"; public override string ShortenedName => "TP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; - public override string Description => @""; + public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d70b26e181..1f94f49598 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = HitObject.StackedPosition; Scale = new Vector2(h.Scale); - Children = new Drawable[] + InternalChildren = new Drawable[] { glow = new GlowPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 79a4714e33..94179f30d3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Blending = BlendingMode.Additive; Origin = Anchor.Centre; - Children = new Drawable[] + InternalChildren = new Drawable[] { new SpriteIcon { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index fb3294d319..9c2d3f5e07 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Container ticks; Container repeatPoints; - Children = new Drawable[] + InternalChildren = new Drawable[] { Body = new SliderBody(s) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 058e3606f4..22bf63814c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables BorderThickness = 2; BorderColour = Color4.White; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 722ab4c6d5..2705c213d9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Spinner = s; - Children = new Drawable[] + InternalChildren = new Drawable[] { circleContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs new file mode 100644 index 0000000000..273422f2e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs @@ -0,0 +1,33 @@ +// 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.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + { + private GameplayCursor cursor; + + public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + + public CursorContainer Cursor => cursor; + + public bool ProvidingUserCursor => true; + + [BackgroundDependencyLoader] + private void load() + { + Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37ca0c021b..dedfa28b7b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -3,9 +3,9 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -14,11 +14,12 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Timing; using OpenTK; +using OpenTK.Graphics; using OpenTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { - internal class CursorTrail : Drawable + internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private int currentIndex; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private float time; + public override bool IsPresent => true; + private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); private const int max_sprites = 2048; @@ -96,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor const int fade_clock_reset_threshold = 1000000; - time = (float)(Time.Current - timeOffset) / 500f; + time = (float)(Time.Current - timeOffset) / 300f; if (time > fade_clock_reset_threshold) resetTime(); } @@ -115,14 +118,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override bool OnMouseMove(InputState state) { + Vector2 pos = state.Mouse.NativeState.Position; + if (lastPosition == null) { - lastPosition = state.Mouse.NativeState.Position; + lastPosition = pos; resampler.AddPosition(lastPosition.Value); return base.OnMouseMove(state); } - foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position)) + foreach (Vector2 pos2 in resampler.AddPosition(pos)) { Trace.Assert(lastPosition.HasValue); @@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNodeSharedData { - public VertexBuffer VertexBuffer; + public VertexBuffer VertexBuffer; } private class TrailDrawNode : DrawNode @@ -188,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override void Draw(Action vertexAction) { if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); + Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); Shader.GetUniform("g_FadeClock").Value = Time; @@ -205,17 +210,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int end = start; Vector2 pos = Parts[i].Position; - ColourInfo colour = DrawInfo.Colour; - colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A; - colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A; - colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A; - colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A; + float time = Parts[i].Time; Texture.DrawQuad( new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), - colour, + DrawInfo.Colour, null, - v => Shared.VertexBuffer.Vertices[end++] = v); + v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = time + 1, + Colour = v.Colour, + }); Parts[i].WasUpdated = false; } @@ -240,5 +247,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Shader.Unbind(); } } + + [StructLayout(LayoutKind.Sequential)] + public struct TexturedTrailVertex : IEquatable, IVertex + { + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 Position; + [VertexMember(4, VertexAttribPointerType.Float)] + public Color4 Colour; + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 TexturePosition; + [VertexMember(1, VertexAttribPointerType.Float)] + public float Time; + + public bool Equals(TexturedTrailVertex other) + { + return Position.Equals(other.Position) + && TexturePosition.Equals(other.TexturePosition) + && Colour.Equals(other.Colour) + && Time.Equals(other.Time); + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 0aeb14514d..34940a084a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -20,13 +20,66 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { protected override Drawable CreateCursor() => new OsuCursor(); + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + public GameplayCursor() { - Add(new CursorTrail { Depth = 1 }); + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CursorTrail { Depth = 1 } + } + }; } private int downCount; + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + break; + } + + return false; + } + + public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + public class OsuCursor : Container { private Container cursorContainer; @@ -131,45 +184,5 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor cursorContainer.Scale = new Vector2(scale); } } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); - break; - } - - return false; - } - - protected override void PopIn() - { - ActiveCursor.FadeTo(1, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - ActiveCursor.FadeTo(0, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(0.6f, 250, Easing.In); - } } } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 4a404e9526..8b7383b6b7 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -131,6 +131,7 @@ + diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs index c50878c6a3..703e6b4f1c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDaycore : ModDaycore { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 1c5e43f411..be6510459e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModEasy : ModEasy { + public override string Description => @"Beats move slower, less accuracy required, and three lives!"; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs index 9813f8b78e..6542b5a844 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHalfTime : ModHalfTime { - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.3; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index ba304c41d8..435a0c1613 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModHardRock : ModHardRock { public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index b0ad43b851..be987a1773 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHidden : ModHidden { - public override string Description => @"The notes fade out before you hit them!"; + public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index ec2385bfba..d5ad04f595 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModRelax : ModRelax { - public override string Description => @"Relax! You will no longer get dizzyfied by ninja-like spinners, demanding drumrolls or unexpected katu's."; + public override string Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's."; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index cf6aa7d895..d3a38289a8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Y; Width = tracker_width; - Children = new[] + InternalChildren = new[] { Tracker = new Box { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 23c34e9863..19a6e4eac2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableBarLineMajor(BarLine barLine) : base(barLine) { - Add(triangleContainer = new Container + AddInternal(triangleContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index bc5abce245..65a4e7bd95 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (state) { case ArmedState.Hit: - Content.ScaleTo(0, 100, Easing.OutQuint).Expire(); + this.ScaleTo(0, 100, Easing.OutQuint).Expire(); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 63e6cfb297..75e1e2a247 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables const float gravity_time = 300; const float gravity_travel_height = 200; - Content.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); + this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out) .Then() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index c9e488764c..37f1300d47 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { FillMode = FillMode.Fit; - Add(bodyContainer = new Container + AddInternal(bodyContainer = new Container { RelativeSizeAxes = Axes.Both, Depth = 1, diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index e57c2f9944..f20ad5b4aa 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Both; Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); - Add(MainPiece = CreateMainPiece()); + InternalChild = MainPiece = CreateMainPiece(); MainPiece.KiaiMode = HitObject.Kiai; } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index b74be134c1..2c46a124d8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var comboColors = decoder.Decode(stream).ComboColors; + var comboColors = decoder.Decode(stream).ComboColours; Color4[] expectedColors = { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 80dea9d01d..c36e825252 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -102,9 +102,9 @@ namespace osu.Game.Tests.Beatmaps.Formats new Color4(255, 187, 255, 255), new Color4(255, 177, 140, 255), }; - Assert.AreEqual(expected.Length, beatmap.ComboColors.Count); + Assert.AreEqual(expected.Length, beatmap.ComboColours.Count); for (int i = 0; i < expected.Length; i++) - Assert.AreEqual(expected[i], beatmap.ComboColors[i]); + Assert.AreEqual(expected[i], beatmap.ComboColours[i]); } [Test] diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index 15bccac172..5fd0f96f4a 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -1,48 +1,36 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Beatmaps; +using osu.Framework.Timing; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] public class TestCaseEditorCompose : OsuTestCase { - private readonly Random random; - private readonly Compose compose; + private DependencyContainer dependencies; - public TestCaseEditorCompose() - { - random = new Random(1337); - - Add(compose = new Compose()); - AddStep("Next beatmap", nextBeatmap); - } - - private OsuGameBase osuGame; - private BeatmapManager beatmaps; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, BeatmapManager beatmaps) + private void load(OsuGameBase osuGame) { - this.osuGame = osuGame; - this.beatmaps = beatmaps; + osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + + var compose = new Compose(); compose.Beatmap.BindTo(osuGame.Beatmap); - } - private void nextBeatmap() - { - var sets = beatmaps.GetAllUsableBeatmapSets(); - if (sets.Count == 0) - return; - - var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; - osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b); + Child = compose; } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs new file mode 100644 index 0000000000..bfdb39dd5e --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs @@ -0,0 +1,464 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +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; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorSeekSnapping : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectComposer) }; + + private Track track; + private HitObjectComposer composer; + + private DecoupleableInterpolatingFramedClock clock; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + + var testBeatmap = new Beatmap + { + ControlPointInfo = new ControlPointInfo + { + TimingPoints = + { + new TimingControlPoint { Time = 0, BeatLength = 200}, + new TimingControlPoint { Time = 100, BeatLength = 400 }, + new TimingControlPoint { Time = 175, BeatLength = 800 }, + new TimingControlPoint { Time = 350, BeatLength = 200 }, + new TimingControlPoint { Time = 450, BeatLength = 100 }, + new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } + } + }, + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 5000 } + } + }; + + osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap); + track = osuGame.Beatmap.Value.Track; + + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { composer = new TestHitObjectComposer(new OsuRuleset()) }, + new Drawable[] { new TimingPointVisualiser(testBeatmap, track) { Clock = clock } }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize), + } + }; + + testSeekNoSnapping(); + testSeekSnappingOnBeat(); + testSeekSnappingInBetweenBeat(); + testSeekForwardNoSnapping(); + testSeekForwardSnappingOnBeat(); + testSeekForwardSnappingFromInBetweenBeat(); + testSeekBackwardSnappingOnBeat(); + testSeekBackwardSnappingFromInBetweenBeat(); + testSeekingWithFloatingPointBeatLength(); + } + + /// + /// Tests whether time is correctly seeked without snapping. + /// + private void testSeekNoSnapping() + { + reset(); + + // Forwards + AddStep("Seek(0)", () => composer.SeekTo(0)); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + AddStep("Seek(33)", () => composer.SeekTo(33)); + AddAssert("Time = 33", () => clock.CurrentTime == 33); + AddStep("Seek(89)", () => composer.SeekTo(89)); + AddAssert("Time = 89", () => clock.CurrentTime == 89); + + // Backwards + AddStep("Seek(25)", () => composer.SeekTo(25)); + AddAssert("Time = 25", () => clock.CurrentTime == 25); + AddStep("Seek(0)", () => composer.SeekTo(0)); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + } + + /// + /// 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() + { + reset(); + + AddStep("Seek(0), Snap", () => composer.SeekTo(0, true)); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + AddStep("Seek(50), Snap", () => composer.SeekTo(50, true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("Seek(100), Snap", () => composer.SeekTo(100, true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("Seek(175), Snap", () => composer.SeekTo(175, true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("Seek(350), Snap", () => composer.SeekTo(350, true)); + AddAssert("Time = 350", () => clock.CurrentTime == 350); + AddStep("Seek(400), Snap", () => composer.SeekTo(400, true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("Seek(450), Snap", () => composer.SeekTo(450, true)); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + } + + /// + /// 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() + { + reset(); + + AddStep("Seek(24), Snap", () => composer.SeekTo(24, true)); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + AddStep("Seek(26), Snap", () => composer.SeekTo(26, true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("Seek(150), Snap", () => composer.SeekTo(150, true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("Seek(170), Snap", () => composer.SeekTo(170, true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("Seek(274), Snap", () => composer.SeekTo(274, true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("Seek(276), Snap", () => composer.SeekTo(276, true)); + AddAssert("Time = 350", () => clock.CurrentTime == 350); + } + + /// + /// 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() + { + reset(); + + AddStep("SeekForward", () => composer.SeekForward()); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("SeekForward", () => composer.SeekForward()); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("SeekForward", () => composer.SeekForward()); + AddAssert("Time = 200", () => clock.CurrentTime == 200); + AddStep("SeekForward", () => composer.SeekForward()); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("SeekForward", () => composer.SeekForward()); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + } + + /// + /// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped. + /// + private void testSeekForwardSnappingOnBeat() + { + reset(); + + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 350", () => clock.CurrentTime == 350); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + } + + /// + /// 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() + { + reset(); + + AddStep("Seek(49)", () => composer.SeekTo(49)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("Seek(49.999)", () => composer.SeekTo(49.999)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("Seek(99)", () => composer.SeekTo(99)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("Seek(99.999)", () => composer.SeekTo(99.999)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("Seek(174)", () => composer.SeekTo(174)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("Seek(349)", () => composer.SeekTo(349)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 350", () => clock.CurrentTime == 350); + AddStep("Seek(399)", () => composer.SeekTo(399)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("Seek(449)", () => composer.SeekTo(449)); + AddStep("SeekForward, Snap", () => composer.SeekForward(true)); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + } + + /// + /// 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() + { + reset(); + + AddStep("Seek(450)", () => composer.SeekTo(450)); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 425", () => clock.CurrentTime == 425); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 375", () => clock.CurrentTime == 375); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 325", () => clock.CurrentTime == 325); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 125", () => clock.CurrentTime == 125); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 25", () => clock.CurrentTime == 25); + AddStep("SeekBackward", () => composer.SeekBackward()); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + } + + /// + /// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped. + /// + private void testSeekBackwardSnappingOnBeat() + { + reset(); + + AddStep("Seek(450)", () => composer.SeekTo(450)); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 350", () => clock.CurrentTime == 350); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 175", () => clock.CurrentTime == 175); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 100", () => clock.CurrentTime == 100); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 50", () => clock.CurrentTime == 50); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 0", () => clock.CurrentTime == 0); + } + + /// + /// 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() + { + reset(); + + AddStep("Seek(451)", () => composer.SeekTo(451)); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + AddStep("Seek(450.999)", () => composer.SeekTo(450.999)); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 450", () => clock.CurrentTime == 450); + AddStep("Seek(401)", () => composer.SeekTo(401)); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + AddStep("Seek(401.999)", () => composer.SeekTo(401.999)); + AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); + AddAssert("Time = 400", () => clock.CurrentTime == 400); + } + + /// + /// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength. + /// + private void testSeekingWithFloatingPointBeatLength() + { + reset(); + + double lastTime = 0; + + AddStep("Seek(0)", () => composer.SeekTo(0)); + + for (int i = 0; i < 20; i++) + { + AddStep("SeekForward, Snap", () => + { + lastTime = clock.CurrentTime; + composer.SeekForward(true); + }); + AddAssert("Time > lastTime", () => clock.CurrentTime > lastTime); + } + + for (int i = 0; i < 20; i++) + { + AddStep("SeekBackward, Snap", () => + { + lastTime = clock.CurrentTime; + composer.SeekBackward(true); + }); + AddAssert("Time < lastTime", () => clock.CurrentTime < lastTime); + } + + AddAssert("Time = 0", () => clock.CurrentTime == 0); + } + + private void reset() + { + AddStep("Reset", () => composer.SeekTo(0)); + } + + private class TestHitObjectComposer : HitObjectComposer + { + public TestHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new ICompositionTool[0]; + } + + private class TimingPointVisualiser : CompositeDrawable + { + private readonly Track track; + + private readonly Drawable tracker; + + public TimingPointVisualiser(Beatmap beatmap, Track track) + { + this.track = track; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Width = 0.75f; + + FillFlowContainer timelineContainer; + + InternalChildren = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(85f) + }, + new Container + { + Name = "Tracks", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + Children = new[] + { + tracker = new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 2, + Colour = Color4.Red, + }, + timelineContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 5) + }, + } + } + }; + + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + + for (int i = 0; i < timingPoints.Count; i++) + { + TimingControlPoint next = i == timingPoints.Count - 1 ? null : timingPoints[i + 1]; + timelineContainer.Add(new TimingPointTimeline(timingPoints[i], next?.Time ?? beatmap.HitObjects.Last().StartTime, track.Length)); + } + } + + protected override void Update() + { + base.Update(); + + tracker.X = (float)(Time.Current / track.Length); + } + + private class TimingPointTimeline : CompositeDrawable + { + public TimingPointTimeline(TimingControlPoint timingPoint, double endTime, double fullDuration) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Box createMainTick(double time) => new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + X = (float)(time / fullDuration), + Height = 10, + Width = 2 + }; + + Box createBeatTick(double time) => new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + X = (float)(time / fullDuration), + Height = 5, + Width = 2, + Colour = time > endTime ? Color4.Gray : Color4.Yellow + }; + + AddInternal(createMainTick(timingPoint.Time)); + AddInternal(createMainTick(endTime)); + + for (double t = timingPoint.Time + timingPoint.BeatLength / 4; t < fullDuration; t += timingPoint.BeatLength / 4) + AddInternal(createBeatTick(t)); + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index a7e104dd81..bbbfef477a 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Timing; using OpenTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; @@ -34,6 +35,11 @@ namespace osu.Game.Tests.Visual typeof(SliderCircleMask) }; + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + [BackgroundDependencyLoader] private void load(OsuGameBase osuGame) { @@ -59,6 +65,10 @@ namespace osu.Game.Tests.Visual }, }); + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + Child = new OsuHitObjectComposer(new OsuRuleset()); } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index 26c8814bc4..bbe2956c5d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -4,30 +4,38 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Framework.Audio.Track; -using osu.Framework.Graphics.Textures; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using OpenTK; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Framework.Configuration; +using osu.Framework.Timing; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] public class TestCaseEditorSummaryTimeline : OsuTestCase { - private const int length = 60000; - private readonly Random random; - public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; private readonly Bindable beatmap = new Bindable(); - public TestCaseEditorSummaryTimeline() + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load() { - random = new Random(1337); + beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); SummaryTimeline summaryTimeline; Add(summaryTimeline = new SummaryTimeline @@ -38,58 +46,6 @@ namespace osu.Game.Tests.Visual }); summaryTimeline.Beatmap.BindTo(beatmap); - - AddStep("New beatmap", newBeatmap); - - newBeatmap(); - } - - private void newBeatmap() - { - var b = new Beatmap(); - - for (int i = 0; i < random.Next(1, 10); i++) - b.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { Time = random.Next(0, length) }); - - for (int i = 0; i < random.Next(1, 5); i++) - b.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = random.Next(0, length) }); - - for (int i = 0; i < random.Next(1, 5); i++) - b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) }); - - for (int i = 0; i < random.Next(1, 5); i++) - b.ControlPointInfo.SamplePoints.Add(new SampleControlPoint { Time = random.Next(0, length) }); - - b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)]; - for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++) - b.BeatmapInfo.Bookmarks[i] = random.Next(0, length); - - beatmap.Value = new TestWorkingBeatmap(b); - } - - private class TestWorkingBeatmap : WorkingBeatmap - { - private readonly Beatmap beatmap; - - public TestWorkingBeatmap(Beatmap beatmap) - : base(beatmap.BeatmapInfo) - { - this.beatmap = beatmap; - } - - protected override Texture GetBackground() => null; - - protected override Beatmap GetBeatmap() => beatmap; - - protected override Track GetTrack() => new TestTrack(); - - private class TestTrack : TrackVirtual - { - public TestTrack() - { - Length = length; - } - } } } } diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs index 43e977ba23..9cdb3e36e3 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs @@ -2,7 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Components; using osu.Game.Tests.Beatmaps; @@ -13,17 +15,28 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCasePlaybackControl : OsuTestCase { - public TestCasePlaybackControl() + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load() { + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + var playback = new PlaybackControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200,100) }; + playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); - Add(playback); + Child = playback; } } } diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index cfa4846939..745ae9ad9d 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -140,12 +140,12 @@ namespace osu.Game.Tests.Visual { Origin = Anchor.Centre; - Add(new Box + InternalChild = new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both - }); + }; switch (direction) { @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - Add(new Box { Size = new Vector2(75) }); + InternalChild = new Box { Size = new Vector2(75) }; } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index a4d2019cd7..df94d5147f 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(new SkipButton(Clock.CurrentTime + 5000)); + Add(new SkipOverlay(Clock.CurrentTime + 5000)); } } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index ed9580211b..80efb0672e 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -130,6 +130,7 @@ + diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 9b00993b6e..93817b9b8f 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO.Serialization; using Newtonsoft.Json; +using osu.Game.Beatmaps.Formats; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -16,14 +17,14 @@ namespace osu.Game.Beatmaps /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap : IJsonSerializable + public class Beatmap : IJsonSerializable, IHasComboColours where T : HitObject { public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); - public List ComboColors = new List + public List ComboColours { get; set; } = new List { new Color4(17, 136, 170, 255), new Color4(102, 136, 0, 255), @@ -55,7 +56,7 @@ namespace osu.Game.Beatmaps BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; - ComboColors = original?.ComboColors ?? ComboColors; + ComboColours = original?.ComboColours ?? ComboColours; HitObjects = original?.HitObjects ?? HitObjects; if (original == null && Metadata == null) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 711e220b88..c35c5df89b 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -50,9 +50,14 @@ namespace osu.Game.Beatmaps protected virtual Beatmap ConvertBeatmap(Beatmap original) { var beatmap = CreateBeatmap(); + + // todo: this *must* share logic (or directly use) Beatmap's constructor. + // right now this isn't easily possible due to generic entanglement. beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); + beatmap.Breaks = original.Breaks; + beatmap.ComboColours = original.ComboColours; return beatmap; } diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs new file mode 100644 index 0000000000..93c6c18eec --- /dev/null +++ b/osu.Game/Beatmaps/Formats/IHasComboColours.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 System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasComboColours + { + List ComboColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs new file mode 100644 index 0000000000..14614a6728 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.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 System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasCustomColours + { + Dictionary CustomColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 915ea9b587..1bb67f9e75 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using System.IO; using System.Linq; -using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -19,7 +18,6 @@ namespace osu.Game.Beatmaps.Formats private Beatmap beatmap; - private bool hasCustomColours; private ConvertHitObjectParser parser; private LegacySampleBank defaultSampleBank; @@ -72,29 +70,28 @@ namespace osu.Game.Beatmaps.Formats { case Section.General: handleGeneral(line); - break; + return; case Section.Editor: handleEditor(line); - break; + return; case Section.Metadata: handleMetadata(line); - break; + return; case Section.Difficulty: handleDifficulty(line); - break; + return; case Section.Events: handleEvents(line); - break; + return; case Section.TimingPoints: handleTimingPoints(line); - break; - case Section.Colours: - handleColours(line); - break; + return; case Section.HitObjects: handleHitObjects(line); - break; + return; } + + base.ParseLine(beatmap, section, line); } private void handleGeneral(string line) @@ -364,38 +361,6 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleColours(string line) - { - var pair = SplitKeyVal(line, ':'); - - string[] split = pair.Value.Split(','); - - if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); - - byte r, g, b; - if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) - throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); - - if (!hasCustomColours) - { - beatmap.ComboColors.Clear(); - hasCustomColours = true; - } - - // Note: the combo index specified in the beatmap is discarded - if (pair.Key.StartsWith(@"Combo")) - { - beatmap.ComboColors.Add(new Color4 - { - R = r / 255f, - G = g / 255f, - B = b / 255f, - A = 1f, - }); - } - } - private void handleHitObjects(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 6a3fb82586..e4aa9f5091 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using OpenTK.Graphics; namespace osu.Game.Beatmaps.Formats { @@ -40,7 +41,53 @@ namespace osu.Game.Beatmaps.Formats protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); - protected abstract void ParseLine(T output, Section section, string line); + protected virtual void ParseLine(T output, Section section, string line) + { + switch (section) + { + case Section.Colours: + handleColours(output, line); + return; + } + } + + private bool hasComboColours; + + private void handleColours(T output, string line) + { + var pair = SplitKeyVal(line, ':'); + + bool isCombo = pair.Key.StartsWith(@"Combo"); + + string[] split = pair.Value.Split(','); + + if (split.Length != 3) + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); + + if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b)) + throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); + + Color4 colour = new Color4(r, g, b, 255); + + if (isCombo) + { + if (!(output is IHasComboColours tHasComboColours)) return; + + if (!hasComboColours) + { + // remove default colours. + tHasComboColours.ComboColours.Clear(); + hasComboColours = true; + } + + tHasComboColours.ComboColours.Add(colour); + } + else + { + if (!(output is IHasCustomColours tHasCustomColours)) return; + tHasCustomColours.CustomColours[pair.Key] = colour; + } + } protected KeyValuePair SplitKeyVal(string line, char separator) { @@ -65,6 +112,7 @@ namespace osu.Game.Beatmaps.Formats Colours, HitObjects, Variables, + Fonts } internal enum LegacySampleBank diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index e35276ae1a..85b0f8d42e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -46,11 +46,13 @@ namespace osu.Game.Beatmaps.Formats { case Section.Events: handleEvents(line); - break; + return; case Section.Variables: handleVariables(line); - break; + return; } + + base.ParseLine(storyboard, section, line); } private void handleEvents(string line) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index bab53cb462..2cb8424bcc 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -8,15 +8,17 @@ using System.Diagnostics; using System.Net; using System.Threading; using osu.Framework.Configuration; +using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Online.API.Requests; using osu.Game.Users; namespace osu.Game.Online.API { - public class APIAccess : IAPIProvider + public class APIAccess : Component, IAPIProvider { + private readonly OsuConfigManager config; private readonly OAuth authentication; public string Endpoint = @"https://osu.ppy.sh"; @@ -25,13 +27,12 @@ namespace osu.Game.Online.API private ConcurrentQueue queue = new ConcurrentQueue(); - public Scheduler Scheduler = new Scheduler(); + /// + /// The username/email provided by the user when initiating a login. + /// + public string ProvidedUsername { get; private set; } - public string Username; - - //private SecurePassword password; - - public string Password; + private string password; public Bindable LocalUser { get; } = new Bindable(createGuestUser()); @@ -41,24 +42,31 @@ namespace osu.Game.Online.API set { authentication.Token = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value); } } - protected bool HasLogin => Token != null || !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); + protected bool HasLogin => Token != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password); // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (should dispose of this or at very least keep a reference). private readonly Thread thread; private readonly Logger log; - public APIAccess() + public APIAccess(OsuConfigManager config) { + this.config = config; + authentication = new OAuth(client_id, client_secret, Endpoint); log = Logger.GetLogger(LoggingTarget.Network); + ProvidedUsername = config.Get(OsuSetting.Username); + Token = config.Get(OsuSetting.Token); + thread = new Thread(run) { IsBackground = true }; thread.Start(); } private readonly List components = new List(); + internal void Schedule(Action action) => base.Schedule(action); + public void Register(IOnlineComponent component) { Scheduler.Add(delegate @@ -111,12 +119,15 @@ namespace osu.Game.Online.API State = APIState.Connecting; - if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) + // save the username at this point, if the user requested for it to be. + config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); + + if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) { //todo: this fails even on network-related issues. we should probably handle those differently. //NotificationOverlay.ShowMessage("Login failed!"); log.Add(@"Login failed!"); - Password = null; + password = null; authentication.Clear(); continue; } @@ -173,8 +184,8 @@ namespace osu.Game.Online.API { Debug.Assert(State == APIState.Offline); - Username = username; - Password = password; + ProvidedUsername = username; + this.password = password; } /// @@ -283,8 +294,8 @@ namespace osu.Game.Online.API public void Logout(bool clearUsername = true) { flushQueue(); - if (clearUsername) Username = null; - Password = null; + if (clearUsername) ProvidedUsername = null; + password = null; authentication.Clear(); LocalUser.Value = createGuestUser(); } @@ -295,9 +306,12 @@ namespace osu.Game.Online.API Id = 1, }; - public void Update() + protected override void Dispose(bool isDisposing) { - Scheduler.Update(); + base.Dispose(isDisposing); + + config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? Token : string.Empty); + config.Save(); } } diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 2dff07a847..0a5210723d 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.API return request; } - private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); }); + private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total)); protected APIDownloadRequest() { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 35af8eefd7..4b05df661b 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Online.API if (checkAndProcessFailure()) return; - api.Scheduler.Add(delegate { Success?.Invoke(); }); + api.Schedule(delegate { Success?.Invoke(); }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); @@ -108,7 +108,7 @@ namespace osu.Game.Online.API { if (API == null || pendingFailure == null) return cancelled; - API.Scheduler.Add(pendingFailure); + API.Schedule(pendingFailure); pendingFailure = null; return true; } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index b3c8774209..4119691c85 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -1,13 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; using osu.Framework.Configuration; using osu.Game.Users; namespace osu.Game.Online.API { - public interface IAPIProvider : IUpdateable + public interface IAPIProvider { /// /// The local user. diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f3c46269d5..45fd45b4b5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -34,7 +34,7 @@ using osu.Game.Skinning; namespace osu.Game { - public class OsuGameBase : Framework.Game, IOnlineComponent, ICanAcceptFiles + public class OsuGameBase : Framework.Game, ICanAcceptFiles { protected OsuConfigManager LocalConfig; @@ -56,8 +56,6 @@ namespace osu.Game protected override string MainResourceFile => @"osu.Game.Resources.dll"; - public APIAccess API; - private Container content; protected override Container Content => content; @@ -108,16 +106,14 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); - dependencies.Cache(API = new APIAccess - { - Username = LocalConfig.Get(OsuSetting.Username), - Token = LocalConfig.Get(OsuSetting.Token) - }); - dependencies.CacheAs(API); + var api = new APIAccess(LocalConfig); + + dependencies.Cache(api); + dependencies.CacheAs(api); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Host)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); @@ -183,9 +179,9 @@ namespace osu.Game lastBeatmap = b; }; - API.Register(this); - FileStore.Cleanup(); + + AddInternal(api); } private void runMigrations() @@ -211,16 +207,6 @@ namespace osu.Game private WorkingBeatmap lastBeatmap; - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - case APIState.Online: - LocalConfig.Set(OsuSetting.Username, LocalConfig.Get(OsuSetting.SaveUsername) ? API.Username : string.Empty); - break; - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -253,24 +239,6 @@ namespace osu.Game base.SetHost(host); } - protected override void Update() - { - base.Update(); - API.Update(); - } - - protected override void Dispose(bool isDisposing) - { - //refresh token may have changed. - if (LocalConfig != null && API != null) - { - LocalConfig.Set(OsuSetting.Token, LocalConfig.Get(OsuSetting.SavePassword) ? API.Token : string.Empty); - LocalConfig.Save(); - } - - base.Dispose(isDisposing); - } - private readonly List fileImporters = new List(); public void Import(params string[] paths) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index e0d806c90f..cba63b4a49 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Direct progressBar.FadeOut(500); }; - request.DownloadProgressed += progress => progressBar.Current.Value = progress; + request.DownloadProgressed += progress => Schedule(() => progressBar.Current.Value = progress); request.Success += data => { diff --git a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs index cbf67893a9..1d9fdab8d5 100644 --- a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Mods public DifficultyIncreaseSection() { - Header = @"Gameplay Difficulty Increase"; + Header = @"Difficulty Increase"; } } } diff --git a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs index c44af8fc4d..651fc222b5 100644 --- a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Mods public DifficultyReductionSection() { - Header = @"Gameplay Difficulty Reduction"; + Header = @"Difficulty Reduction"; } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 03c1f0468c..4765787caf 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -27,14 +27,8 @@ namespace osu.Game.Overlays.Mods public string Header { - get - { - return headerLabel.Text; - } - set - { - headerLabel.Text = value; - } + get => headerLabel.Text; + set => headerLabel.Text = value; } public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); @@ -47,12 +41,12 @@ namespace osu.Game.Overlays.Mods { if (m == null) return new ModButtonEmpty(); - else - return new ModButton(m) - { - SelectedColour = selectedColour, - SelectionChanged = Action, - }; + + return new ModButton(m) + { + SelectedColour = selectedColour, + SelectionChanged = Action, + }; }).ToArray(); ButtonsContainer.Children = modContainers; @@ -65,10 +59,7 @@ namespace osu.Game.Overlays.Mods private Color4 selectedColour = Color4.White; public Color4 SelectedColour { - get - { - return selectedColour; - } + get => selectedColour; set { if (value == selectedColour) return; @@ -102,31 +93,31 @@ namespace osu.Game.Overlays.Mods { Mod selected = button.SelectedMod; if (selected == null) continue; - foreach (Type type in modTypes) + foreach (var type in modTypes) if (type.IsInstanceOfType(selected)) { if (immediate) button.Deselect(); else - Scheduler.AddDelayed(() => button.Deselect(), delay += 50); + Scheduler.AddDelayed(button.Deselect, delay += 50); } } } /// - /// Select one or more mods in this section. + /// Select one or more mods in this section and deselects all other ones. /// - /// The types of s which should be deselected. - public void SelectTypes(IEnumerable mods) + /// The types of s which should be selected. + public void SelectTypes(IEnumerable modTypes) { foreach (var button in buttons) { - for (int i = 0; i < button.Mods.Length; i++) - { - foreach (var mod in mods) - if (mod.GetType().IsInstanceOfType(button.Mods[i])) - button.SelectAt(i); - } + int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); + + if (i >= 0) + button.SelectAt(i); + else + button.Deselect(); } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d0a507be98..fe7b7bae99 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Mods private void selectedModsChanged(IEnumerable obj) { foreach (ModSection section in ModSectionsContainer.Children) - section.SelectTypes(obj); + section.SelectTypes(obj.Select(m => m.GetType()).ToList()); updateMods(); } @@ -287,7 +287,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Action = modButtonPressed, }, - new AssistedSection + new SpecialSection { RelativeSizeAxes = Axes.X, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Mods/AssistedSection.cs b/osu.Game/Overlays/Mods/SpecialSection.cs similarity index 82% rename from osu.Game/Overlays/Mods/AssistedSection.cs rename to osu.Game/Overlays/Mods/SpecialSection.cs index 978b12da19..75b2462ff5 100644 --- a/osu.Game/Overlays/Mods/AssistedSection.cs +++ b/osu.Game/Overlays/Mods/SpecialSection.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class AssistedSection : ModSection + public class SpecialSection : ModSection { protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; public override ModType ModType => ModType.Special; @@ -19,9 +19,9 @@ namespace osu.Game.Overlays.Mods SelectedColour = colours.BlueLight; } - public AssistedSection() + public SpecialSection() { - Header = @"Assisted"; + Header = @"Special"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index a5d068adbd..4a4fc7363e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -210,7 +210,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { PlaceholderText = "Email address", RelativeSizeAxes = Axes.X, - Text = api?.Username ?? string.Empty, + Text = api?.ProvidedUsername ?? string.Empty, TabbableContentContainer = this }, password = new OsuPasswordTextBox diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3dd8d503ed..ae1c8af1a4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -5,9 +5,12 @@ 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.Input; using osu.Framework.Logging; +using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Tools; @@ -27,21 +30,28 @@ namespace osu.Game.Rulesets.Edit private RulesetContainer rulesetContainer; private readonly List layerContainers = new List(); + private readonly Bindable beatmap = new Bindable(); + + private IAdjustableClock adjustableClock; + protected HitObjectComposer(Ruleset ruleset) { this.ruleset = ruleset; + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load(OsuGameBase osuGame, IAdjustableClock adjustableClock, IFrameBasedClock framedClock) { + this.adjustableClock = adjustableClock; + + beatmap.BindTo(osuGame.Beatmap); + try { - rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); - - // TODO: should probably be done at a RulesetContainer level to share logic with Player. - rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock()); + rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value); + rulesetContainer.Clock = framedClock; } catch (Exception e) { @@ -134,6 +144,112 @@ namespace osu.Game.Rulesets.Edit }); } + protected override bool OnWheel(InputState state) + { + if (state.Mouse.WheelDelta > 0) + SeekBackward(true); + else + SeekForward(true); + return true; + } + + /// + /// Seeks the current time one beat-snapped beat-length backwards. + /// + /// Whether to snap to the closest beat. + public void SeekBackward(bool snapped = false) => seek(-1, snapped); + + /// + /// Seeks the current time one beat-snapped beat-length forwards. + /// + /// Whether to snap to the closest beat. + public void SeekForward(bool snapped = false) => seek(1, snapped); + + private void seek(int direction, bool snapped) + { + // Todo: This should not be a constant, but feels good for now + const int beat_snap_divisor = 4; + + var cpi = beatmap.Value.Beatmap.ControlPointInfo; + + var timingPoint = cpi.TimingPointAt(adjustableClock.CurrentTime); + if (direction < 0 && timingPoint.Time == adjustableClock.CurrentTime) + { + // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into + int activeIndex = cpi.TimingPoints.IndexOf(timingPoint); + while (activeIndex > 0 && adjustableClock.CurrentTime == timingPoint.Time) + timingPoint = cpi.TimingPoints[--activeIndex]; + } + + double seekAmount = timingPoint.BeatLength / beat_snap_divisor; + double seekTime = adjustableClock.CurrentTime + seekAmount * direction; + + if (!snapped || cpi.TimingPoints.Count == 0) + { + adjustableClock.Seek(seekTime); + return; + } + + // We will be snapping to beats within timingPoint + seekTime -= timingPoint.Time; + + // Determine the index from timingPoint of the closest beat to seekTime, accounting for scrolling direction + int closestBeat; + if (direction > 0) + closestBeat = (int)Math.Floor(seekTime / seekAmount); + else + closestBeat = (int)Math.Ceiling(seekTime / seekAmount); + + seekTime = timingPoint.Time + closestBeat * seekAmount; + + // Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this. + // Instead, we'll go to the next beat in the direction when this is the case + if (Precision.AlmostEquals(adjustableClock.CurrentTime, seekTime)) + { + closestBeat += direction > 0 ? 1 : -1; + seekTime = timingPoint.Time + closestBeat * seekAmount; + } + + if (seekTime < timingPoint.Time && timingPoint != cpi.TimingPoints.First()) + seekTime = timingPoint.Time; + + var nextTimingPoint = cpi.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + if (seekTime > nextTimingPoint?.Time) + seekTime = nextTimingPoint.Time; + + adjustableClock.Seek(seekTime); + } + + public void SeekTo(double seekTime, bool snapped = false) + { + // Todo: This should not be a constant, but feels good for now + const int beat_snap_divisor = 4; + + if (!snapped) + { + adjustableClock.Seek(seekTime); + return; + } + + var timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(seekTime); + double beatSnapLength = timingPoint.BeatLength / beat_snap_divisor; + + // We will be snapping to beats within the timing point + seekTime -= timingPoint.Time; + + // Determine the index from the current timing point of the closest beat to seekTime + int closestBeat = (int)Math.Round(seekTime / beatSnapLength); + seekTime = timingPoint.Time + closestBeat * beatSnapLength; + + // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to + // the next timing point's start time + var nextTimingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + if (seekTime > nextTimingPoint?.Time) + seekTime = nextTimingPoint.Time; + + adjustableClock.Seek(seekTime); + } + private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 3356a56c33..9f45cada7e 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Autoplay"; public override string ShortenedName => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; - public override string Description => "Watch a perfect automated play through the song"; + public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 0; public bool AllowFail => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index c0480b0647..015f7381fb 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mods public override string ShortenedName => "CN"; public override bool HasImplementation => false; public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override string Description => "Watch the video without visual distractions."; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 180199cd70..da4263875b 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Daycore"; public override string ShortenedName => "DC"; public override FontAwesome Icon => FontAwesome.fa_question; - public override string Description => "whoaaaaa"; + public override string Description => "Whoaaaaa..."; public override void ApplyToClock(IAdjustableClock clock) { diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 0b8f4b0b5b..6225a6feee 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -7,18 +7,16 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public class ModDoubleTime : Mod, IApplicableToClock + public abstract class ModDoubleTime : Mod, IApplicableToClock { public override string Name => "Double Time"; public override string ShortenedName => "DT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Zoooooooooom"; + public override string Description => "Zoooooooooom..."; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; - public override double ScoreMultiplier => 1.12; - public virtual void ApplyToClock(IAdjustableClock clock) { clock.Rate = 1.5; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 5c5b9b1b44..7037edfa31 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -13,7 +13,6 @@ namespace osu.Game.Rulesets.Mods public override string ShortenedName => "EZ"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; public override ModType Type => ModType.DifficultyReduction; - public override string Description => "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index bb9ed0047d..883225a66b 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -13,12 +13,10 @@ namespace osu.Game.Rulesets.Mods public override string ShortenedName => "HT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; public override ModType Type => ModType.DifficultyReduction; - public override string Description => "Less zoom"; + public override string Description => "Less zoom..."; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; - public override double ScoreMultiplier => 1.12; - public virtual void ApplyToClock(IAdjustableClock clock) { clock.Rate = 0.75; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index c4c0f38faf..c998bc123f 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mods public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; + public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; public void ApplyToDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index ad4df55b91..c2925f440f 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Nightcore"; public override string ShortenedName => "NC"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; - public override string Description => "uguuuuuuuu"; + public override string Description => "Uguuuuuuuu..."; public override void ApplyToClock(IAdjustableClock clock) { diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 9686eff99c..8a849825a2 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModNoFail : Mod, IApplicableFailOverride { - public override string Name => "NoFail"; + public override string Name => "No Fail"; public override string ShortenedName => "NF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public override ModType Type => ModType.DifficultyReduction; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index bb12b2e39f..08942fbe12 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.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 osu.Game.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods @@ -9,6 +10,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Perfect"; public override string ShortenedName => "PF"; + public override FontAwesome Icon => FontAwesome.fa_question; public override string Description => "SS or quit."; protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 490825220c..ef9ff4c69e 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods public override string ShortenedName => "SD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Miss a note and fail."; + public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index 1de5297e22..5548313f8e 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => string.Empty; public override string ShortenedName => string.Empty; public override string Description => string.Empty; - public override double ScoreMultiplier => 0.0; + public override double ScoreMultiplier => 0; public Mod[] Mods; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 394b6fa9fd..02f88d9ee0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -19,7 +19,7 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { - public abstract class DrawableHitObject : Container, IHasAccentColour + public abstract class DrawableHitObject : CompositeDrawable, IHasAccentColour { public readonly HitObject HitObject; diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 05e47ef5b1..fe2549d300 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; +using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -17,10 +18,15 @@ namespace osu.Game.Screens.Edit.Components { public class PlaybackControl : BottomBarContainer { - private readonly IconButton playButton; + private IconButton playButton; - public PlaybackControl() + private IAdjustableClock adjustableClock; + + [BackgroundDependencyLoader] + private void load(IAdjustableClock adjustableClock) { + this.adjustableClock = adjustableClock; + PlaybackTabControl tabs; Children = new Drawable[] @@ -54,22 +60,22 @@ namespace osu.Game.Screens.Edit.Components } }; - tabs.Current.ValueChanged += newValue => Track.Tempo.Value = newValue; + tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; } private void togglePause() { - if (Track.IsRunning) - Track.Stop(); + if (adjustableClock.IsRunning) + adjustableClock.Stop(); else - Track.Start(); + adjustableClock.Start(); } protected override void Update() { base.Update(); - playButton.Icon = Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; } private class PlaybackTabControl : OsuTabControl diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 9a78e6e189..5a3b6c652b 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -4,17 +4,20 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using System; +using osu.Framework.Allocation; +using osu.Framework.Timing; namespace osu.Game.Screens.Edit.Components { public class TimeInfoContainer : BottomBarContainer { - private const int count_duration = 150; - private readonly OsuSpriteText trackTimer; + private IAdjustableClock adjustableClock; + public TimeInfoContainer() { + Children = new Drawable[] { trackTimer = new OsuSpriteText @@ -28,11 +31,17 @@ namespace osu.Game.Screens.Edit.Components }; } + [BackgroundDependencyLoader] + private void load(IAdjustableClock adjustableClock) + { + this.adjustableClock = adjustableClock; + } + protected override void Update() { base.Update(); - trackTimer.Text = TimeSpan.FromMilliseconds(Track.CurrentTime).ToString(@"mm\:ss\:fff"); + trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff"); } } } 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 c7f40327a9..b249713581 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -19,8 +20,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { private readonly Drawable marker; - public MarkerPart() + private readonly IAdjustableClock adjustableClock; + + public MarkerPart(IAdjustableClock adjustableClock) { + this.adjustableClock = adjustableClock; + Add(marker = new MarkerVisualisation()); } @@ -53,12 +58,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length); } - private void seekTo(double time) => Beatmap.Value?.Track.Seek(time); + private void seekTo(double time) => adjustableClock.Seek(time); protected override void Update() { base.Update(); - marker.X = (float)(Beatmap.Value?.Track.CurrentTime ?? 0); + marker.X = (float)adjustableClock.CurrentTime; } protected override void LoadBeatmap(WorkingBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 8a472dc357..0e80c13257 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -16,15 +17,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary /// public class SummaryTimeline : BottomBarContainer { - private readonly Drawable timelineBar; - - public SummaryTimeline() + [BackgroundDependencyLoader] + private void load(OsuColour colours, IAdjustableClock adjustableClock) { TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; - Children = new[] + Children = new Drawable[] { - markerPart = new MarkerPart { RelativeSizeAxes = Axes.Both }, + markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, controlPointPart = new ControlPointPart { Anchor = Anchor.Centre, @@ -39,9 +39,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary RelativeSizeAxes = Axes.Both, Height = 0.35f }, - timelineBar = new Container + new Container { RelativeSizeAxes = Axes.Both, + Colour = colours.Gray5, Children = new Drawable[] { new Circle @@ -80,11 +81,5 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary bookmarkPart.Beatmap.BindTo(Beatmap); breakPart.Beatmap.BindTo(Beatmap); } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - timelineBar.Colour = colours.Gray5; - } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bb43099352..8b651000fd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -12,6 +12,7 @@ using osu.Game.Screens.Edit.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Framework.Allocation; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Timing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Screens; using osu.Game.Screens.Edit.Screens.Compose; @@ -26,13 +27,27 @@ namespace osu.Game.Screens.Edit public override bool ShowOverlaysOnEnter => false; - private readonly Box bottomBackground; - private readonly Container screenContainer; + private Box bottomBackground; + private Container screenContainer; private EditorScreen currentScreen; - public Editor() + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { + // TODO: should probably be done at a RulesetContainer level to share logic with Player. + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + var adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + adjustableClock.ChangeSource(sourceClock); + + dependencies.CacheAs(adjustableClock); + dependencies.CacheAs(adjustableClock); + EditorMenuBar menuBar; TimeInfoContainer timeInfo; SummaryTimeline timeline; @@ -130,12 +145,9 @@ namespace osu.Game.Screens.Edit timeline.Beatmap.BindTo(Beatmap); playback.Beatmap.BindTo(Beatmap); menuBar.Mode.ValueChanged += onModeChanged; - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { bottomBackground.Colour = colours.Gray2; + } private void exportBeatmap() diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index d42c0bfdac..861a08fb07 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -1,13 +1,13 @@ // 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 OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Screens.Edit.Screens.Compose @@ -17,9 +17,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose private const float vertical_margins = 10; private const float horizontal_margins = 20; - private readonly Container composerContainer; + private Container composerContainer; - public Compose() + [BackgroundDependencyLoader] + private void load() { ScrollableTimeline timeline; Children = new Drawable[] @@ -75,14 +76,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose }; timeline.Beatmap.BindTo(Beatmap); - Beatmap.ValueChanged += beatmapChanged; - } - private void beatmapChanged(WorkingBeatmap newBeatmap) - { - composerContainer.Clear(); - - var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance(); + var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); if (ruleset == null) { Logger.Log("Beatmap doesn't have a ruleset assigned."); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 63b5538ad7..46b09e2c23 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -51,7 +51,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private SelectionBox currentSelectionBox; - public void AddSelectionOverlay() => AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer)); + public void AddSelectionOverlay() + { + if (overlayContainer.Count > 0) + AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer)); + } public void RemoveSelectionOverlay() { diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 615c124ea7..29b68abc21 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -18,7 +18,7 @@ using System.Collections.Generic; namespace osu.Game.Screens.Play { - public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition + public abstract class GameplayMenuOverlay : OverlayContainer { private const int transition_duration = 200; private const int button_height = 70; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c8ff261a93..8502812f26 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play Alpha = 0, }, RulesetContainer, - new SkipButton(firstObjectTime) + new SkipOverlay(firstObjectTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 784dcf7657..31e7313c0b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,12 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; @@ -21,7 +23,6 @@ namespace osu.Game.Screens.Play private Player player; private BeatmapMetadataDisplay info; - private VisualSettings visualSettings; private bool showOverlays = true; public override bool ShowOverlaysOnEnter => showOverlays; @@ -46,7 +47,8 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }); - Add(visualSettings = new VisualSettings + + Add(new VisualSettings { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -93,7 +95,7 @@ namespace osu.Game.Screens.Play contentIn(); info.Delay(750).FadeIn(500); - this.Delay(2150).Schedule(pushWhenLoaded); + this.Delay(1800).Schedule(pushWhenLoaded); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -109,29 +111,65 @@ namespace osu.Game.Screens.Play logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); } + private bool weHandledMouseDown; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + weHandledMouseDown = true; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + weHandledMouseDown = false; + return base.OnMouseUp(state, args); + } + + private ScheduledDelegate pushDebounce; + + private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && (!GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed || weHandledMouseDown); + private void pushWhenLoaded() { - if (player.LoadState != LoadState.Ready || visualSettings.IsHovered) + if (!IsCurrentScreen) return; + + try + { + if (!readyForPush) + { + // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce + // if we become unready for push during the delay. + pushDebounce?.Cancel(); + pushDebounce = null; + return; + } + + if (pushDebounce != null) + return; + + pushDebounce = Scheduler.AddDelayed(() => + { + contentOut(); + + this.Delay(250).Schedule(() => + { + if (!IsCurrentScreen) return; + + if (!Push(player)) + Exit(); + else + { + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + } + }); + }, 500); + } + finally { Schedule(pushWhenLoaded); - return; } - - contentOut(); - - this.Delay(250).Schedule(() => - { - if (!IsCurrentScreen) return; - - if (!Push(player)) - Exit(); - else - { - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. - ValidForResume = false; - } - }); } protected override bool OnExiting(Screen next) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 95b464154a..e0de89535e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -133,5 +134,8 @@ namespace osu.Game.Screens.Play.PlayerSettings } protected override Container Content => content; + + protected override bool OnHover(InputState state) => true; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; } } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipOverlay.cs similarity index 89% rename from osu.Game/Screens/Play/SkipButton.cs rename to osu.Game/Screens/Play/SkipOverlay.cs index 08bb26c72b..19ee0cb989 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -21,7 +21,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipButton : OverlayContainer, IKeyBindingHandler + public class SkipOverlay : OverlayContainer, IKeyBindingHandler { private readonly double startTime; @@ -35,8 +35,9 @@ namespace osu.Game.Screens.Play private double displayTime; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + protected override bool BlockPassThroughMouse => false; - public SkipButton(double startTime) + public SkipOverlay(double startTime) { this.startTime = startTime; @@ -51,12 +52,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre; } - protected override bool OnMouseMove(InputState state) - { - fadeContainer.State = Visibility.Visible; - return base.OnMouseMove(state); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -121,15 +116,9 @@ namespace osu.Game.Screens.Play Expire(); } - protected override void PopIn() - { - this.FadeIn(); - } + protected override void PopIn() => this.FadeIn(); - protected override void PopOut() - { - this.FadeOut(); - } + protected override void PopOut() => this.FadeOut(); protected override void Update() { @@ -137,6 +126,13 @@ namespace osu.Game.Screens.Play remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); } + protected override bool OnMouseMove(InputState state) + { + if (!state.Mouse.HasAnyButtonPressed) + fadeContainer.State = Visibility.Visible; + return base.OnMouseMove(state); + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -176,7 +172,7 @@ namespace osu.Game.Screens.Play if (stateChanged) this.FadeIn(500, Easing.OutExpo); - if (!IsHovered) + if (!IsHovered && !IsDragged) using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; @@ -194,6 +190,18 @@ namespace osu.Game.Screens.Play base.LoadComplete(); State = Visibility.Visible; } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + scheduledHide?.Cancel(); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + State = Visibility.Visible; + return base.OnMouseUp(state, args); + } } private class Button : OsuClickableContainer @@ -274,7 +282,7 @@ namespace osu.Game.Screens.Play flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint); box.FadeColour(colourHover, 500, Easing.OutQuint); background.FadeTo(0.4f, 500, Easing.OutQuint); - return base.OnHover(state); + return true; } protected override void OnHoverLost(InputState state) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index c347bfe70f..09524d2eac 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Select if (removeAutoModOnResume) { var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType(); - SelectedMods.Value = SelectedMods.Value.Where(m => m.GetType() != autoType).ToArray(); + modSelect.DeselectTypes(new[] { autoType }, true); removeAutoModOnResume = false; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bbb1bcfdd5..49ce648f71 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -268,6 +268,8 @@ + + @@ -555,7 +557,7 @@ - + @@ -811,7 +813,7 @@ - +