diff --git a/osu-framework b/osu-framework index 8c4f232694..fac688633b 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 8c4f23269447d9ce21a5dbd3a0fd4f6caae9ab38 +Subproject commit fac688633b8fcf34ae5d0514c26b03e217161eb4 diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index d0d623178e..808faa511b 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests { - public class CatchBeatmapConversionTest : BeatmapConversionTest + internal class CatchBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); } - public struct ConvertValue : IEquatable + internal struct ConvertValue : IEquatable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Tests && Precision.AlmostEquals(Position, other.Position, conversion_lenience); } - public class TestCatchRuleset : CatchRuleset + internal class TestCatchRuleset : CatchRuleset { } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 15e51fa126..2325a8cad9 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Catch { diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs similarity index 85% rename from osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs rename to osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f47d09fe20..f8351b7519 100644 --- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -1,10 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps; using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; -namespace osu.Game.Rulesets.Catch +namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 95ffd41518..548813fbd2 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Objects Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; } + + protected override HitWindows CreateHitWindows() => null; } public enum FruitVisualRepresentation diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index f1ee874b88..bd67a7d96a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests { - public class ManiaBeatmapConversionTest : BeatmapConversionTest + internal class ManiaBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); } - public struct ConvertValue : IEquatable + internal struct ConvertValue : IEquatable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Tests && Column == other.Column; } - public class TestManiaRuleset : ManiaRuleset + internal class TestManiaRuleset : ManiaRuleset { } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs index 281c2789af..a4109722d4 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs @@ -4,6 +4,8 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Tests.Visual; @@ -17,6 +19,14 @@ namespace osu.Game.Rulesets.Mania.Tests { public TestCaseManiaHitObjects() { + Note note1 = new Note(); + Note note2 = new Note(); + HoldNote holdNote = new HoldNote { StartTime = 1000 }; + + note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + Add(new FillFlowContainer { Anchor = Anchor.Centre, @@ -43,14 +53,14 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableNote(new Note(), ManiaAction.Key1) + new DrawableNote(note1, ManiaAction.Key1) { Y = 5000, LifetimeStart = double.MinValue, LifetimeEnd = double.MaxValue, AccentColour = Color4.Red }, - new DrawableNote(new Note(), ManiaAction.Key1) + new DrawableNote(note2, ManiaAction.Key1) { Y = 6000, LifetimeStart = double.MinValue, @@ -77,13 +87,13 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) + new DrawableHoldNote(holdNote, ManiaAction.Key1) { Y = 5000, Height = 1000, LifetimeStart = double.MinValue, LifetimeEnd = double.MaxValue, - AccentColour = Color4.Red + AccentColour = Color4.Red, } } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs index 053f478027..dff2b2d56a 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs @@ -8,6 +8,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; @@ -83,13 +85,16 @@ namespace osu.Game.Rulesets.Mania.Tests int col = rng.Next(0, 4); - var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1) + var note = new Note { Column = col }; + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableNote = new DrawableNote(note, ManiaAction.Key1) { AccentColour = playfield.Columns.ElementAt(col).AccentColour }; - playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); - playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); + playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect }); + playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect }); }); } @@ -162,32 +167,24 @@ namespace osu.Game.Rulesets.Mania.Tests for (double t = start_time; t <= start_time + duration; t += 100) { - playfield.Add(new DrawableNote(new Note - { - StartTime = t, - Column = 0 - }, ManiaAction.Key1)); + var note1 = new Note { StartTime = t, Column = 0 }; + var note2 = new Note { StartTime = t, Column = 3 }; - playfield.Add(new DrawableNote(new Note - { - StartTime = t, - Column = 3 - }, ManiaAction.Key4)); + note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + playfield.Add(new DrawableNote(note1, ManiaAction.Key1)); + playfield.Add(new DrawableNote(note2, ManiaAction.Key4)); } - playfield.Add(new DrawableHoldNote(new HoldNote - { - StartTime = start_time, - Duration = duration, - Column = 1 - }, ManiaAction.Key2)); + var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 }; + var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 }; - playfield.Add(new DrawableHoldNote(new HoldNote - { - StartTime = start_time, - Duration = duration, - Column = 2 - }, ManiaAction.Key3)); + holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2)); + playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3)); } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c8a7402904..4f7c52860f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original) { - BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); @@ -85,7 +84,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps yield break; foreach (ManiaHitObject obj in objects) + { + obj.HitWindows = original.HitWindows; yield return obj; + } } private readonly List prevNoteTimes = new List(max_notes_for_density); diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs similarity index 98% rename from osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs rename to osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 822ba53eeb..ac45130fd8 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -1,14 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; -using System; -using System.Collections.Generic; -namespace osu.Game.Rulesets.Mania +namespace osu.Game.Rulesets.Mania.Difficulty { internal class ManiaDifficultyCalculator : DifficultyCalculator { diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f1d65f855b..b1702de537 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -15,7 +15,9 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty; namespace osu.Game.Rulesets.Mania { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 4cf22ccd39..12e3d2de51 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -103,6 +103,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// Lenience of release hit windows. This is to make cases where the hold note release /// is timed alongside presses of other hit objects less awkward. + /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps /// private const double release_window_lenience = 1.5; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index aa7de4ed01..3d54043027 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -15,7 +15,7 @@ using OpenTK; namespace osu.Game.Rulesets.Osu.Tests { - public class OsuBeatmapConversionTest : BeatmapConversionTest + internal class OsuBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); } - public struct ConvertValue : IEquatable + internal struct ConvertValue : IEquatable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience); } - public class TestOsuRuleset : OsuRuleset + internal class TestOsuRuleset : OsuRuleset { } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index f7f73f74a5..cb1ea5cc5f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -93,12 +93,36 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset()); AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1)); + + AddStep("Distance Overflow", () => testDistanceOverflow()); + AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1)); } private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + private void testDistanceOverflow(int repeats = 0) + { + var slider = new Slider + { + StartTime = Time.Current + 1000, + Position = new Vector2(239, 176), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(154, 28), + new Vector2(52, -34) + }, + Distance = 700, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats), + StackHeight = 10 + }; + + addSlider(slider, 2, 2); + } + private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 5f3a909488..4369a31b2c 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -40,7 +40,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps RepeatSamples = curveData.RepeatSamples, RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false + NewCombo = comboData?.NewCombo ?? false, + HitWindows = original.HitWindows }; } else if (endTimeData != null) @@ -50,8 +51,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, EndTime = endTimeData.EndTime, - Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, + HitWindows = original.HitWindows }; } else @@ -61,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false + NewCombo = comboData?.NewCombo ?? false, + HitWindows = original.HitWindows }; } } diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs similarity index 92% rename from osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs rename to osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4853cd66cd..bd8d18af41 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -4,12 +4,13 @@ using System; using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; -using osu.Game.Rulesets.Osu.OsuDifficulty.Skills; -namespace osu.Game.Rulesets.Osu.OsuDifficulty +namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs similarity index 98% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs rename to osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs index 5c8ab0f3d4..e97042a9aa 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -5,7 +5,7 @@ using System.Collections; using System.Collections.Generic; using osu.Game.Rulesets.Osu.Objects; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing +namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { /// /// An enumerable container wrapping input as diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs similarity index 98% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs rename to osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 415f76ced8..f95426466b 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -3,10 +3,10 @@ using System; using System.Linq; -using OpenTK; using osu.Game.Rulesets.Osu.Objects; +using OpenTK; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing +namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { /// /// A wrapper around extending it with additional data required for difficulty calculation. diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs similarity index 85% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 5c736d7bb5..0a45c62671 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +namespace osu.Game.Rulesets.Osu.Difficulty.Skills { /// /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs similarity index 96% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs index 983599432f..47037c1503 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Utils; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; -using osu.Game.Rulesets.Osu.OsuDifficulty.Utils; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +namespace osu.Game.Rulesets.Osu.Difficulty.Skills { /// /// Used to processes strain values of s, keep track of strain levels caused by the processed objects diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs similarity index 93% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index ae3caa1e66..b807f20037 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +namespace osu.Game.Rulesets.Osu.Difficulty.Skills { /// /// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit. diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs similarity index 98% rename from osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs rename to osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs index f6933a3e5d..55bd950209 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs @@ -5,7 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils +namespace osu.Game.Rulesets.Osu.Difficulty.Utils { /// /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 69a54fb533..a2423ffbe5 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; @@ -19,7 +18,9 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Difficulty; namespace osu.Game.Rulesets.Osu { diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index 6b9214d9dc..938060a664 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -18,7 +19,17 @@ namespace osu.Game.Rulesets.Osu.Scoring private readonly int beatmapMaxCombo; private Mod[] mods; + + /// + /// Approach rate adjusted by mods. + /// private double realApproachRate; + + /// + /// Overall difficulty adjusted by mods. + /// + private double realOverallDifficulty; + private double accuracy; private int scoreMaxCombo; private int count300; @@ -32,7 +43,8 @@ namespace osu.Game.Rulesets.Osu.Scoring countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); beatmapMaxCombo = Beatmap.HitObjects.Count(); - beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1; + // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above) + beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); } public override double Calculate(Dictionary categoryRatings = null) @@ -57,8 +69,12 @@ namespace osu.Game.Rulesets.Osu.Scoring ar = Math.Min(10, ar * 1.4); if (mods.Any(m => m is OsuModEasy)) ar = Math.Max(0, ar / 2); - double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450); + + double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate; + double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; + realOverallDifficulty = (80 - 0.5 - hitWindow300) / 6; // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things @@ -84,6 +100,9 @@ namespace osu.Game.Rulesets.Osu.Scoring categoryRatings.Add("Aim", aimValue); categoryRatings.Add("Speed", speedValue); categoryRatings.Add("Accuracy", accuracyValue); + categoryRatings.Add("OD", realOverallDifficulty); + categoryRatings.Add("AR", realApproachRate); + categoryRatings.Add("Max Combo", beatmapMaxCombo); } return totalValue; @@ -120,8 +139,9 @@ namespace osu.Game.Rulesets.Osu.Scoring aimValue *= approachRateFactor; + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.18f; + aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11. if (mods.Any(h => h is OsuModFlashlight)) { @@ -132,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Scoring // Scale the aim value with accuracy _slightly_ aimValue *= 0.5f + accuracy / 2.0f; // It is important to also consider accuracy difficulty when doing that - aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; + aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500; return aimValue; } @@ -152,10 +172,13 @@ namespace osu.Game.Rulesets.Osu.Scoring if (beatmapMaxCombo > 0) speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + if (mods.Any(m => m is OsuModHidden)) + speedValue *= 1.18f; + // Scale the speed value with accuracy _slightly_ speedValue *= 0.5f + accuracy / 2.0f; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; + speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500; return speedValue; } @@ -177,7 +200,7 @@ namespace osu.Game.Rulesets.Osu.Scoring // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; + double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index 33a5e1772e..ca4fc3ec57 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests { - public class TaikoBeatmapConversionTest : BeatmapConversionTest + internal class TaikoBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); } - public struct ConvertValue : IEquatable + internal struct ConvertValue : IEquatable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Tests && IsStrong == other.IsStrong; } - public class TestTaikoRuleset : TaikoRuleset + internal class TestTaikoRuleset : TaikoRuleset { } } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d40e8dc643..b450e4d26c 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -132,7 +132,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { StartTime = j, Samples = currentSamples, - IsStrong = strong + IsStrong = strong, + HitWindows = obj.HitWindows }; } else @@ -142,6 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps StartTime = j, Samples = currentSamples, IsStrong = strong, + HitWindows = obj.HitWindows }; } @@ -157,6 +159,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps IsStrong = strong, Duration = taikoDuration, TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4, + HitWindows = obj.HitWindows }; } } @@ -171,6 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps IsStrong = strong, Duration = endTimeData.Duration, RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), + HitWindows = obj.HitWindows }; } else @@ -184,6 +188,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, IsStrong = strong, + HitWindows = obj.HitWindows }; } else @@ -193,6 +198,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, IsStrong = strong, + HitWindows = obj.HitWindows }; } } diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs similarity index 98% rename from osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs rename to osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index f14c53f7ae..acff0d286d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -1,12 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; -using System.Collections.Generic; using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Taiko.Objects; -namespace osu.Game.Rulesets.Taiko +namespace osu.Game.Rulesets.Taiko.Difficulty { internal class TaikoDifficultyCalculator : DifficultyCalculator { diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 64219c7b52..4c9ec5473b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Objects /// private double tickSpacing = 100; + private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -47,9 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / TickRate; - - RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); - RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); + overallDifficulty = difficulty.OverallDifficulty; } protected override void CreateNestedHitObjects() @@ -57,6 +57,9 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(); createTicks(); + + RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); + RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); } private void createTicks() diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 102de5717f..35dc17c0e2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -13,7 +13,9 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Difficulty; namespace osu.Game.Rulesets.Taiko { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 6e0cf6be2e..489c38c420 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -9,6 +9,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Tests.Resources; using OpenTK; @@ -117,7 +118,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestParity(string beatmap) { var legacy = decode(beatmap, out Beatmap json); - json.ShouldDeepEqual(legacy); + json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert(); } /// diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs new file mode 100644 index 0000000000..6fa49c4edb --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseHoldToConfirmOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay) }; + + public TestCaseHoldToConfirmOverlay() + { + bool fired = false; + + var firedText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Fired!", + TextSize = 50, + Alpha = 0, + }; + + var overlay = new TestHoldToConfirmOverlay + { + Action = () => + { + fired = true; + firedText.FadeTo(1).Then().FadeOut(1000); + } + }; + + Children = new Drawable[] + { + overlay, + firedText + }; + + AddStep("start confirming", () => overlay.Begin()); + AddStep("abort confirming", () => overlay.Abort()); + + AddAssert("ensure aborted", () => !fired); + + AddStep("start confirming", () => overlay.Begin()); + + AddUntilStep(() => fired, "wait until confirmed"); + } + + private class TestHoldToConfirmOverlay : ExitConfirmOverlay + { + protected override bool AllowMultipleFires => true; + + public void Begin() => BeginConfirm(); + public void Abort() => AbortConfirm(); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs index 233c418d4a..123c1fe055 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs @@ -4,6 +4,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; +using osu.Framework.Configuration.Tracking; +using osu.Framework.Graphics; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -11,36 +13,82 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseOnScreenDisplay : OsuTestCase { - private FrameworkConfigManager config; - private Bindable frameSyncMode; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new OnScreenDisplay()); - - frameSyncMode = config.GetBindable(FrameworkSetting.FrameSync); - - FrameSync initial = frameSyncMode.Value; - - AddRepeatStep(@"Change frame limiter", setNextMode, 3); - - AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial); - } - - private void setNextMode() - { - var nextMode = frameSyncMode.Value + 1; - if (nextMode > FrameSync.Unlimited) - nextMode = FrameSync.VSync; - frameSyncMode.Value = nextMode; - } - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config) + private void load() { - this.config = config; + var config = new TestConfigManager(); + + var osd = new TestOnScreenDisplay(); + osd.BeginTracking(this, config); + Add(osd); + + AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); + AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); + AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); + AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeybind), 3); + } + + private class TestConfigManager : ConfigManager + { + public TestConfigManager() + { + InitialiseDefaults(); + } + + protected override void InitialiseDefaults() + { + Set(TestConfigSetting.ToggleSettingNoKeybind, false); + Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1); + Set(TestConfigSetting.ToggleSettingWithKeybind, false); + Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1); + + base.InitialiseDefaults(); + } + + public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get(setting)); + + public void IncrementEnumSetting(TestConfigSetting setting) + { + var nextValue = Get(setting) + 1; + if (nextValue > EnumSetting.Setting4) + nextValue = EnumSetting.Setting1; + Set(setting, nextValue); + } + + public override TrackedSettings CreateTrackedSettings() => new TrackedSettings + { + new TrackedSetting(TestConfigSetting.ToggleSettingNoKeybind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")), + new TrackedSetting(TestConfigSetting.EnumSettingNoKeybind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())), + new TrackedSetting(TestConfigSetting.ToggleSettingWithKeybind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")), + new TrackedSetting(TestConfigSetting.EnumSettingWithKeybind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")), + }; + + protected override void PerformLoad() + { + } + + protected override bool PerformSave() => false; + } + + private enum TestConfigSetting + { + ToggleSettingNoKeybind, + EnumSettingNoKeybind, + ToggleSettingWithKeybind, + EnumSettingWithKeybind + } + + private enum EnumSetting + { + Setting1, + Setting2, + Setting3, + Setting4 + } + + private class TestOnScreenDisplay : OnScreenDisplay + { + protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110); } } } diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs similarity index 89% rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs rename to osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index f477a0b97e..a64e60dbd6 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -15,18 +16,19 @@ using OpenTK; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseScreenBreadcrumbs : OsuTestCase + public class TestCaseScreenBreadcrumbControl : OsuTestCase { - private readonly ScreenBreadcrumbControl breadcrumbs; - private TestScreen currentScreen, changedScreen; + private readonly ScreenBreadcrumbControl breadcrumbs; + private Screen currentScreen, changedScreen; - public TestCaseScreenBreadcrumbs() + public TestCaseScreenBreadcrumbControl() { TestScreen startScreen; OsuSpriteText titleText; Children = new Drawable[] { + changedScreen = currentScreen = startScreen = new TestScreenOne(), new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -35,23 +37,21 @@ namespace osu.Game.Tests.Visual Spacing = new Vector2(10), Children = new Drawable[] { - breadcrumbs = new ScreenBreadcrumbControl + breadcrumbs = new ScreenBreadcrumbControl(startScreen) { RelativeSizeAxes = Axes.X, }, titleText = new OsuSpriteText(), }, }, - currentScreen = startScreen = new TestScreenOne(), }; - breadcrumbs.OnScreenChanged += s => + breadcrumbs.Current.ValueChanged += s => { titleText.Text = $"Changed to {s.ToString()}"; changedScreen = s; }; - AddStep(@"make start current", () => breadcrumbs.CurrentScreen = startScreen); assertCurrent(); pushNext(); assertCurrent(); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual breadcrumbs.StripColour = colours.Blue; } - private void pushNext() => AddStep(@"push next screen", () => currentScreen = currentScreen.PushNext()); + private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext()); private void assertCurrent() => AddAssert(@"assert the current screen is correct", () => currentScreen == changedScreen); private abstract class TestScreen : OsuScreen diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index da52dc7284..8094abe5ed 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 509622c2fe..b3082e49de 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Configuration; +using osu.Framework.Configuration.Tracking; using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Screens.Select; @@ -95,6 +96,11 @@ namespace osu.Game.Configuration public OsuConfigManager(Storage storage) : base(storage) { } + + public override TrackedSettings CreateTrackedSettings() => new TrackedSettings + { + new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")) + }; } public enum OsuSetting diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 2a30e0d032..f657c0cae5 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -44,19 +44,6 @@ namespace osu.Game.Graphics.Containers return base.OnClick(state); } - protected override bool OnDragStart(InputState state) - { - if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) - { - State = Visibility.Hidden; - return true; - } - - return base.OnDragStart(state); - } - - protected override bool OnDrag(InputState state) => State == Visibility.Hidden; - private void onStateChanged(Visibility visibility) { switch (visibility) diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index fbdb27a81c..ba3927b771 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -1,78 +1,52 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface { - public class ScreenBreadcrumbControl : ScreenBreadcrumbControl + /// + /// A which follows the active screen (and allows navigation) in a stack. + /// + public class ScreenBreadcrumbControl : BreadcrumbControl { - } + private Screen last; - public class ScreenBreadcrumbControl : BreadcrumbControl where T : Screen - { - private T currentScreen; - public T CurrentScreen + public ScreenBreadcrumbControl(Screen initialScreen) { - get { return currentScreen; } - set + Current.ValueChanged += newScreen => { - if (value == currentScreen) return; - - if (CurrentScreen != null) - { - CurrentScreen.Exited -= onExited; - CurrentScreen.ModePushed -= onPushed; - } - else - { - // this is the first screen in the stack, so call the initial onPushed - currentScreen = value; - onPushed(CurrentScreen); - } - - currentScreen = value; - - if (CurrentScreen != null) - { - CurrentScreen.Exited += onExited; - CurrentScreen.ModePushed += onPushed; - Current.Value = CurrentScreen; - OnScreenChanged?.Invoke(CurrentScreen); - } - } - } - - public event Action OnScreenChanged; - - public ScreenBreadcrumbControl() - { - Current.ValueChanged += s => - { - if (s != CurrentScreen) - { - CurrentScreen = s; - s.MakeCurrent(); - } + if (last != newScreen && !newScreen.IsCurrentScreen) + newScreen.MakeCurrent(); }; + + onPushed(initialScreen); } - private void onExited(Screen screen) + private void screenChanged(Screen newScreen) { - CurrentScreen = screen as T; + if (last != null) + { + last.Exited -= screenChanged; + last.ModePushed -= onPushed; + } + + last = newScreen; + + newScreen.Exited += screenChanged; + newScreen.ModePushed += onPushed; + + Current.Value = newScreen; } private void onPushed(Screen screen) { - var newScreen = screen as T; - Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem); - AddItem(newScreen); + AddItem(screen); - CurrentScreen = newScreen; + screenChanged(screen); } } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index dd8f00f6cd..1658bb4e79 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -26,7 +26,8 @@ namespace osu.Game.Input.Bindings { new KeyBinding(InputKey.F8, GlobalAction.ToggleChat), new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), - new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot), + new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons), + new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), @@ -76,6 +77,8 @@ namespace osu.Game.Input.Bindings QuickRetry, [Description("Take screenshot")] - TakeScreenshot + TakeScreenshot, + [Description("Toggle gameplay mouse buttons")] + ToggleGameplayMouseButtons, } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d443ed36ae..fe5ca4f278 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -466,6 +466,9 @@ namespace osu.Game case GlobalAction.ToggleDirect: direct.ToggleVisibility(); return true; + case GlobalAction.ToggleGameplayMouseButtons: + LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); + return true; } return false; diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs new file mode 100644 index 0000000000..a0e4bf1a39 --- /dev/null +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + /// + /// An overlay which will display a black screen that dims over a period before confirming an exit action. + /// Action is BYO (derived class will need to call and from a user event). + /// + public abstract class HoldToConfirmOverlay : Container + { + public Action Action; + + private Box overlay; + + private const int activate_delay = 400; + private const int fadeout_delay = 200; + + private bool fired; + + /// + /// Whether the overlay should be allowed to return from a fired state. + /// + protected virtual bool AllowMultipleFires => false; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + + Children = new Drawable[] + { + overlay = new Box + { + Alpha = 0, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + } + }; + } + + protected void BeginConfirm() + { + if (!AllowMultipleFires && fired) return; + overlay.FadeIn(activate_delay * (1 - overlay.Alpha), Easing.Out).OnComplete(_ => + { + Action?.Invoke(); + fired = true; + }); + } + + protected void AbortConfirm() + { + if (!AllowMultipleFires && fired) return; + overlay.FadeOut(fadeout_delay, Easing.Out); + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index e80a469f91..7406a9ec4f 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input; using OpenTK.Graphics; @@ -43,7 +44,7 @@ namespace osu.Game.Overlays.KeyBinding } private OsuSpriteText text; - private OsuSpriteText pressAKey; + private OsuTextFlowContainer pressAKey; private FillFlowContainer buttons; @@ -95,10 +96,11 @@ namespace osu.Game.Overlays.KeyBinding Anchor = Anchor.TopRight, Origin = Anchor.TopRight }, - pressAKey = new OsuSpriteText + pressAKey = new OsuTextFlowContainer { - Text = "Press a key to change binding, DEL to delete, ESC to cancel.", - Y = height, + Text = "Press a key to change binding, Shift+Delete to delete, Escape to cancel.", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Margin = new MarginPadding(padding), Alpha = 0, Colour = colours.YellowDark @@ -204,9 +206,16 @@ namespace osu.Game.Overlays.KeyBinding finalise(); return true; case Key.Delete: - bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); - return true; + { + if (state.Keyboard.ShiftPressed) + { + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + return true; + } + + break; + } } bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); @@ -223,6 +232,26 @@ namespace osu.Game.Overlays.KeyBinding return true; } + protected override bool OnJoystickPress(InputState state, Framework.Input.JoystickEventArgs args) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + finalise(); + + return true; + } + + protected override bool OnJoystickRelease(InputState state, Framework.Input.JoystickEventArgs args) + { + if (!HasFocus) + return base.OnJoystickRelease(state, args); + + finalise(); + return true; + } + private void finalise() { if (bindTarget != null) @@ -241,7 +270,7 @@ namespace osu.Game.Overlays.KeyBinding GetContainingInputManager().ChangeFocus(null); pressAKey.FadeOut(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; + pressAKey.Padding = new MarginPadding { Top = height, Bottom = -pressAKey.DrawHeight }; } protected override void OnFocus(InputState state) @@ -250,7 +279,7 @@ namespace osu.Game.Overlays.KeyBinding AutoSizeEasing = Easing.OutQuint; pressAKey.FadeIn(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding(); + pressAKey.Padding = new MarginPadding { Top = height }; updateBindTarget(); base.OnFocus(state); diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index d63babf3b6..8c8ff89420 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -4,7 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Music { public class PlaylistList : CompositeDrawable { - public Action OnSelect; + public Action Selected; + public Action OrderChanged; private readonly ItemsScrollContainer items; @@ -25,7 +27,8 @@ namespace osu.Game.Overlays.Music InternalChild = items = new ItemsScrollContainer { RelativeSizeAxes = Axes.Both, - OnSelect = set => OnSelect?.Invoke(set) + Selected = set => Selected?.Invoke(set), + OrderChanged = (s, i) => OrderChanged?.Invoke(s, i) }; } @@ -35,34 +38,20 @@ namespace osu.Game.Overlays.Music set { base.Padding = value; } } - public IEnumerable BeatmapSets - { - get { return items.Sets; } - set { items.Sets = value; } - } - public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; - public BeatmapSetInfo NextSet => items.NextSet; - public BeatmapSetInfo PreviousSet => items.PreviousSet; - - public BeatmapSetInfo SelectedSet - { - get { return items.SelectedSet; } - set { items.SelectedSet = value; } - } - - public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet); public void Filter(string searchTerm) => items.SearchTerm = searchTerm; private class ItemsScrollContainer : OsuScrollContainer { - public Action OnSelect; + public Action Selected; + public Action OrderChanged; private readonly SearchContainer search; private readonly FillFlowContainer items; + private readonly IBindable beatmapBacking = new Bindable(); + public ItemsScrollContainer() { Children = new Drawable[] @@ -83,14 +72,36 @@ namespace osu.Game.Overlays.Music }; } - public IEnumerable Sets + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps, OsuGameBase osuGame) { - get { return items.Select(x => x.BeatmapSetInfo).ToList(); } - set - { - items.Clear(); - value.ForEach(AddBeatmapSet); - } + beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); + beatmaps.ItemAdded += addBeatmapSet; + beatmaps.ItemRemoved += removeBeatmapSet; + + beatmapBacking.BindTo(osuGame.Beatmap); + beatmapBacking.ValueChanged += _ => updateSelectedSet(); + } + + private void addBeatmapSet(BeatmapSetInfo obj) + { + var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }; + + items.Add(newItem); + items.SetLayoutPosition(newItem, items.Count - 1); + } + + private void removeBeatmapSet(BeatmapSetInfo obj) + { + var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); + if (itemToRemove != null) + items.Remove(itemToRemove); + } + + private void updateSelectedSet() + { + foreach (PlaylistItem s in items.Children) + s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo.ID; } public string SearchTerm @@ -99,34 +110,7 @@ namespace osu.Game.Overlays.Music set { search.SearchTerm = value; } } - public void AddBeatmapSet(BeatmapSetInfo beatmapSet) - { - var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) }; - - items.Add(newItem); - items.SetLayoutPosition(newItem, items.Count); - } - - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) - { - var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID); - if (itemToRemove != null) - items.Remove(itemToRemove); - } - - public BeatmapSetInfo SelectedSet - { - get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; } - set - { - foreach (PlaylistItem s in items.Children) - s.Selected = s.BeatmapSetInfo.ID == value?.ID; - } - } - public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; - public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo; - public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo; private Vector2 nativeDragPosition; private PlaylistItem draggedItem; @@ -227,6 +211,7 @@ namespace osu.Game.Overlays.Music } items.SetLayoutPosition(draggedItem, dstIndex); + OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex); } private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 3496c044fb..76c2222f8b 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -19,18 +19,16 @@ namespace osu.Game.Overlays.Music public class PlaylistOverlay : OverlayContainer { private const float transition_duration = 600; - private const float playlist_height = 510; + public Action OrderChanged; + + private BeatmapManager beatmaps; private FilterControl filter; private PlaylistList list; - private BeatmapManager beatmaps; - private readonly Bindable beatmapBacking = new Bindable(); - public IEnumerable BeatmapSets => list.BeatmapSets; - [BackgroundDependencyLoader] private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) { @@ -60,7 +58,8 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, - OnSelect = itemSelected, + Selected = itemSelected, + OrderChanged = (s, i) => OrderChanged?.Invoke(s, i) }, filter = new FilterControl { @@ -74,30 +73,16 @@ namespace osu.Game.Overlays.Music }, }; - beatmaps.ItemAdded += handleBeatmapAdded; - beatmaps.ItemRemoved += handleBeatmapRemoved; - - list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); - beatmapBacking.BindTo(game.Beatmap); filter.Search.OnCommit = (sender, newText) => { - var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); - if (beatmap != null) playSpecified(beatmap); + BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (beatmap != null) + beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap); }; } - protected override void LoadComplete() - { - base.LoadComplete(); - beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo; - beatmapBacking.TriggerChange(); - } - - private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo)); - private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo)); - protected override void PopIn() { filter.Search.HoldFocus = true; @@ -123,49 +108,7 @@ namespace osu.Game.Overlays.Music return; } - playSpecified(set.Beatmaps.First()); - } - - public void PlayPrevious() - { - var playable = list.PreviousSet; - - if (playable != null) - { - playSpecified(playable.Beatmaps.First()); - list.SelectedSet = playable; - } - } - - public void PlayNext() - { - var playable = list.NextSet; - - if (playable != null) - { - playSpecified(playable.Beatmaps.First()); - list.SelectedSet = playable; - } - } - - private void playSpecified(BeatmapInfo info) - { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking); - - var track = beatmapBacking.Value.Track; - - track.Restart(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - { - beatmaps.ItemAdded -= handleBeatmapAdded; - beatmaps.ItemRemoved -= handleBeatmapRemoved; - } + beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b4021f2808..fb4e278b0c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -50,7 +51,10 @@ namespace osu.Game.Overlays private LocalisationEngine localisation; + private BeatmapManager beatmaps; private readonly Bindable beatmapBacking = new Bindable(); + private List beatmapSets; + private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; @@ -93,8 +97,9 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation) + private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) { + this.beatmaps = beatmaps; this.localisation = localisation; Children = new Drawable[] @@ -111,6 +116,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.X, Y = player_height + 10, + OrderChanged = playlistOrderChanged }, playerContainer = new Container { @@ -185,7 +191,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = next, + Action = () => next(), Icon = FontAwesome.fa_step_forward, }, } @@ -214,11 +220,24 @@ namespace osu.Game.Overlays } }; + beatmapSets = beatmaps.GetAllUsableBeatmapSets(); + beatmaps.ItemAdded += handleBeatmapAdded; + beatmaps.ItemRemoved += handleBeatmapRemoved; + beatmapBacking.BindTo(game.Beatmap); playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } + private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index) + { + beatmapSets.Remove(beatmapSetInfo); + beatmapSets.Insert(index, beatmapSetInfo); + } + + private void handleBeatmapAdded(BeatmapSetInfo obj) => beatmapSets.Add(obj); + private void handleBeatmapRemoved(BeatmapSetInfo obj) => beatmapSets.RemoveAll(s => s.ID == obj.ID); + protected override void LoadComplete() { beatmapBacking.ValueChanged += beatmapChanged; @@ -257,7 +276,7 @@ namespace osu.Game.Overlays playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any()) + if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any()) next(); } else @@ -271,7 +290,7 @@ namespace osu.Game.Overlays if (track == null) { if (!beatmapBacking.Disabled) - playlist.PlayNext(); + next(true); return; } @@ -284,13 +303,26 @@ namespace osu.Game.Overlays private void prev() { queuedDirection = TransformDirection.Prev; - playlist.PlayPrevious(); + + var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + if (playable != null) + { + beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); + beatmapBacking.Value.Track.Restart(); + } } - private void next() + private void next(bool instant = false) { - queuedDirection = TransformDirection.Next; - playlist.PlayNext(); + if (!instant) + queuedDirection = TransformDirection.Next; + + var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + if (playable != null) + { + beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); + beatmapBacking.Value.Track.Restart(); + } } private WorkingBeatmap current; @@ -314,8 +346,8 @@ namespace osu.Game.Overlays else { //figure out the best direction based on order in playlist. - var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); + var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); + var next = beatmap == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); direction = last > next ? TransformDirection.Prev : TransformDirection.Next; } diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 3dd088891d..1c80f2e626 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays @@ -30,6 +31,7 @@ namespace osu.Game.Overlays private readonly SpriteText textLine3; private const float height = 110; + private const float height_notext = 98; private const float height_contracted = height * 0.9f; private readonly FillFlowContainer optionLights; @@ -101,12 +103,12 @@ namespace osu.Game.Overlays }, textLine3 = new OsuSpriteText { - Padding = new MarginPadding { Bottom = 15 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Bottom = 15 }, Font = @"Exo2.0-Bold", TextSize = 12, Alpha = 0.3f, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, }, } } @@ -116,9 +118,10 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) + private void load(FrameworkConfigManager frameworkConfig, OsuConfigManager osuConfig) { BeginTracking(this, frameworkConfig); + BeginTracking(this, osuConfig); } private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>(); @@ -175,13 +178,10 @@ namespace osu.Game.Overlays textLine2.Text = description.Value; textLine3.Text = description.Shortcut.ToUpper(); - box.Animate( - b => b.FadeIn(500, Easing.OutQuint), - b => b.ResizeHeightTo(height, 500, Easing.OutQuint) - ).Then( - b => b.FadeOutFromOne(1500, Easing.InQuint), - b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint) - ); + if (string.IsNullOrEmpty(textLine3.Text)) + textLine3.Text = "NO KEY BOUND"; + + Display(box); int optionCount = 0; int selectedOption = -1; @@ -213,6 +213,17 @@ namespace osu.Game.Overlays }); } + protected virtual void Display(Drawable toDisplay) + { + toDisplay.Animate( + b => b.FadeIn(500, Easing.OutQuint), + b => b.ResizeHeightTo(height, 500, Easing.OutQuint) + ).Then( + b => b.FadeOutFromOne(1500, Easing.InQuint), + b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint) + ); + } + private class OptionLight : Container { private Color4 glowingColour, idleColour; diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index da63495fec..f922c507f7 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -100,6 +100,8 @@ namespace osu.Game.Overlays public bool Adjust(GlobalAction action) { + if (!IsLoaded) return false; + switch (action) { case GlobalAction.DecreaseVolume: diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs similarity index 89% rename from osu.Game/Beatmaps/DifficultyCalculator.cs rename to osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 37155c09cd..070bc7ddb0 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -2,19 +2,20 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Game.Rulesets.Mods; -using osu.Framework.Timing; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; -namespace osu.Game.Beatmaps +namespace osu.Game.Rulesets.Difficulty { public abstract class DifficultyCalculator { protected readonly IBeatmap Beatmap; protected readonly Mod[] Mods; - protected double TimeRate = 1; + protected double TimeRate { get; private set; } = 1; protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) { diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs similarity index 63% rename from osu.Game/Rulesets/Scoring/PerformanceCalculator.cs rename to osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 5b8f5f0d0f..9fd7a0156d 100644 --- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -2,9 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; -namespace osu.Game.Rulesets.Scoring +namespace osu.Game.Rulesets.Difficulty { public abstract class PerformanceCalculator { @@ -14,6 +19,8 @@ namespace osu.Game.Rulesets.Scoring protected readonly IBeatmap Beatmap; protected readonly Score Score; + protected double TimeRate { get; private set; } = 1; + protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) { Score = score; @@ -22,6 +29,15 @@ namespace osu.Game.Rulesets.Scoring var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); diffCalc.Calculate(attributes); + + ApplyMods(score.Mods); + } + + protected virtual void ApplyMods(Mod[] mods) + { + var clock = new StopwatchClock(); + mods.OfType().ForEach(m => m.ApplyToClock(clock)); + TimeRate = clock.Rate; } public abstract double Calculate(Dictionary categoryDifficulty = null); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 13fa61f536..cd612a5387 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -51,16 +51,10 @@ namespace osu.Game.Rulesets.Objects private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - private HitWindows hitWindows; - /// /// The hit windows for this . /// - public HitWindows HitWindows - { - get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty)); - protected set => hitWindows = value; - } + public HitWindows HitWindows { get; set; } private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); @@ -78,7 +72,11 @@ namespace osu.Game.Rulesets.Objects nestedHitObjects.Clear(); CreateNestedHitObjects(); - nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty)); + nestedHitObjects.ForEach(h => + { + h.HitWindows = HitWindows; + h.ApplyDefaults(controlPointInfo, difficulty); + }); } protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) @@ -89,8 +87,9 @@ namespace osu.Game.Rulesets.Objects Kiai = effectPoint.KiaiMode; SampleControlPoint = samplePoint; - overallDifficulty = difficulty.OverallDifficulty; - hitWindows = null; + if (HitWindows == null) + HitWindows = CreateHitWindows(); + HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } protected virtual void CreateNestedHitObjects() @@ -98,5 +97,14 @@ namespace osu.Game.Rulesets.Objects } protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); + + /// + /// Creates the for this . + /// This can be null to indicate that the has no . + /// + /// This will only be invoked if hasn't been set externally (e.g. from a . + /// + /// + protected virtual HitWindows CreateHitWindows() => new HitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index bf0878a408..7610593d6a 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Objects public bool AllowsOk; /// - /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window. + /// Sets hit windows with values that correspond to a difficulty parameter. /// /// The parameter. - public HitWindows(double difficulty) + public virtual void SetDifficulty(double difficulty) { Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index 0db5a1dff1..939d3b9c93 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public float X { get; set; } public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs new file mode 100644 index 0000000000..131492ea12 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + public class ConvertHitWindows : HitWindows + { + private static readonly IReadOnlyDictionary base_ranges = new Dictionary + { + { HitResult.Perfect, (44.8, 38.8, 27.8) }, + { HitResult.Great, (128, 98, 68 ) }, + { HitResult.Good, (194, 164, 134) }, + { HitResult.Ok, (254, 224, 194) }, + { HitResult.Meh, (302, 272, 242) }, + { HitResult.Miss, (376, 346, 316) }, + }; + + public override void SetDifficulty(double difficulty) + { + Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); + Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); + Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); + Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); + Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); + Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index e3b35e2f8e..22abc64b60 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -12,5 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public double EndTime { get; set; } public double Duration => EndTime - StartTime; + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index 32fb197c62..6bca5b717c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public float X { get; set; } public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index c9b3046698..1dc826af9b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public double Duration => EndTime - StartTime; public float X { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index f83173f498..23955b2d23 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs new file mode 100644 index 0000000000..fd86173372 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs @@ -0,0 +1,28 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + public class ConvertHitWindows : HitWindows + { + private static readonly IReadOnlyDictionary base_ranges = new Dictionary + { + { HitResult.Great, (160, 100, 40) }, + { HitResult.Good, (280, 200, 120) }, + { HitResult.Meh, (400, 300, 200) }, + { HitResult.Miss, (400, 400, 400) }, + }; + + public override void SetDifficulty(double difficulty) + { + Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); + Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); + Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); + Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index c6033d482c..35b8c1c7dd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 28aac6862e..73b8369aca 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -20,5 +20,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float X => Position.X; public float Y => Position.Y; + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 72d18664bf..11db086778 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko internal sealed class ConvertHit : HitObject, IHasCombo { public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs new file mode 100644 index 0000000000..6fbf7e122f --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs @@ -0,0 +1,28 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + public class ConvertHitWindows : HitWindows + { + private static readonly IReadOnlyDictionary base_ranges = new Dictionary + { + { HitResult.Great, (100, 70, 40) }, + { HitResult.Good, (240, 160, 100) }, + { HitResult.Meh, (270, 190, 140) }, + { HitResult.Miss, (400, 400, 400) }, + }; + + public override void SetDifficulty(double difficulty) + { + Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); + Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); + Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); + Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index e810e687bd..95c69222b5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo { public bool NewCombo { get; set; } + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 193e50aed6..7baea212ea 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko public double EndTime { get; set; } public double Duration => EndTime - StartTime; + + protected override HitWindows CreateHitWindows() => new ConvertHitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs index 86fe74f9af..3932d8ed9d 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -99,11 +99,9 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Add(l); } - //TODO: Figure out if the following code is needed in some cases. Judging by the map - // "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this. // Lengthen slider curves that are too short compared to what's // in the .osu file. - /*if (l < Length && calculatedPath.Count > 1) + if (l < Distance && calculatedPath.Count > 1) { Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; double d = diff.Length; @@ -111,9 +109,9 @@ namespace osu.Game.Rulesets.Objects if (d <= 0) return; - calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d); - cumulativeLength[calculatedPath.Count - 1] = Length; - }*/ + calculatedPath[calculatedPath.Count - 1] += diff * (float)((Distance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = Distance; + } } public void Calculate() diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 6883d319f4..395eeab419 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets { diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 67a9a59d4a..1847b63658 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets try { var assembly = Assembly.LoadFrom(file); - loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset))); + loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } catch (Exception) { diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs new file mode 100644 index 0000000000..bfb2b7c13b --- /dev/null +++ b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Scoring.Legacy +{ + /// + /// A which retrieves the applicable and + /// for the score from the database. + /// + public class DatabasedLegacyScoreParser : LegacyScoreParser + { + private readonly RulesetStore rulesets; + private readonly BeatmapManager beatmaps; + + public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance(); + protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash)); + } +} diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs index d5ab856697..a90cd79186 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs @@ -14,17 +14,8 @@ using System.Linq; namespace osu.Game.Rulesets.Scoring.Legacy { - public class LegacyScoreParser + public abstract class LegacyScoreParser { - private readonly RulesetStore rulesets; - private readonly BeatmapManager beatmaps; - - public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) - { - this.rulesets = rulesets; - this.beatmaps = beatmaps; - } - private IBeatmap currentBeatmap; private Ruleset currentRuleset; @@ -34,33 +25,35 @@ namespace osu.Game.Rulesets.Scoring.Legacy using (SerializationReader sr = new SerializationReader(stream)) { - score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) }; - currentRuleset = score.Ruleset.CreateInstance(); + currentRuleset = GetRuleset(sr.ReadByte()); + score = new Score { Ruleset = currentRuleset.RulesetInfo }; /* score.Pass = true;*/ var version = sr.ReadInt32(); /* score.FileChecksum = */ - var beatmapHash = sr.ReadString(); - score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash); - currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap; + currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap; + score.Beatmap = currentBeatmap.BeatmapInfo; /* score.PlayerName = */ score.User = new User { Username = sr.ReadString() }; /* var localScoreChecksum = */ sr.ReadString(); - /* score.Count300 = */ - sr.ReadUInt16(); - /* score.Count100 = */ - sr.ReadUInt16(); - /* score.Count50 = */ - sr.ReadUInt16(); - /* score.CountGeki = */ - sr.ReadUInt16(); - /* score.CountKatu = */ - sr.ReadUInt16(); - /* score.CountMiss = */ - sr.ReadUInt16(); + + var count300 = sr.ReadUInt16(); + var count100 = sr.ReadUInt16(); + var count50 = sr.ReadUInt16(); + var countGeki = sr.ReadUInt16(); + var countKatu = sr.ReadUInt16(); + var countMiss = sr.ReadUInt16(); + + score.Statistics[HitResult.Great] = count300; + score.Statistics[HitResult.Good] = count100; + score.Statistics[HitResult.Meh] = count50; + score.Statistics[HitResult.Perfect] = countGeki; + score.Statistics[HitResult.Ok] = countKatu; + score.Statistics[HitResult.Miss] = countMiss; + score.TotalScore = sr.ReadInt32(); score.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ @@ -81,6 +74,34 @@ namespace osu.Game.Rulesets.Scoring.Legacy /*OnlineId =*/ sr.ReadInt32(); + switch (score.Ruleset.ID) + { + case 0: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; + break; + } + case 1: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; + break; + } + case 2: + { + int totalHits = count50 + count100 + count300 + countMiss + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1; + break; + } + case 3: + { + int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; + break; + } + } + using (var replayInStream = new MemoryStream(compressedReplay)) { byte[] properties = new byte[5]; @@ -150,5 +171,19 @@ namespace osu.Game.Rulesets.Scoring.Legacy return frame; } + + /// + /// Retrieves the for a specific id. + /// + /// The id. + /// The . + protected abstract Ruleset GetRuleset(int rulesetId); + + /// + /// Retrieves the corresponding to an MD5 hash. + /// + /// The MD5 hash. + /// The . + protected abstract WorkingBeatmap GetBeatmap(string md5Hash); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 957fd037e0..69d25fcb67 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring public Score ReadReplayFile(string replayFilename) { using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) - return new LegacyScoreParser(rulesets, beatmaps).Parse(s); + return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(s); } } } diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs new file mode 100644 index 0000000000..62605da5a4 --- /dev/null +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input; +using osu.Game.Overlays; +using OpenTK.Input; + +namespace osu.Game.Screens.Menu +{ + public class ExitConfirmOverlay : HoldToConfirmOverlay + { + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Key == Key.Escape && !args.Repeat) + { + BeginConfirm(); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + { + if (args.Key == Key.Escape) + { + AbortConfirm(); + return true; + } + + return base.OnKeyUp(state, args); + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 907ad81111..e564ab786d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -39,6 +39,10 @@ namespace osu.Game.Screens.Menu Children = new Drawable[] { + new ExitConfirmOverlay + { + Action = Exit, + }, new ParallaxContainer { ParallaxAmount = 0.01f, diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index a018a2697a..926a96eb6c 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -1,50 +1,19 @@ // 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 System; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; -using OpenTK.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Play { - public class HotkeyRetryOverlay : Container, IKeyBindingHandler + public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler { - public Action Action; - - private Box overlay; - - private const int activate_delay = 400; - private const int fadeout_delay = 200; - - private bool fired; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - - Children = new Drawable[] - { - overlay = new Box - { - Alpha = 0, - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - } - }; - } - public bool OnPressed(GlobalAction action) { if (action != GlobalAction.QuickRetry) return false; - overlay.FadeIn(activate_delay, Easing.Out); + BeginConfirm(); return true; } @@ -52,18 +21,8 @@ namespace osu.Game.Screens.Play { if (action != GlobalAction.QuickRetry) return false; - overlay.FadeOut(fadeout_delay, Easing.Out); + AbortConfirm(); return true; } - - protected override void Update() - { - base.Update(); - if (!fired && overlay.Alpha == 1) - { - fired = true; - Action?.Invoke(); - } - } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f397d0c3d4..4a46279d30 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Play hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; }, OnResume = () => hudOverlay.KeyCounter.IsCounting = true, - Children = new Drawable[] + Children = new[] { storyboardContainer = new Container { @@ -174,12 +174,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Child = RulesetContainer }, - new SkipOverlay(firstObjectTime) + new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor) { - Clock = Clock, // skip button doesn't want to use the audio clock directly + Anchor = Anchor.Centre, + Origin = Anchor.Centre, ProcessCustomClock = false, - AdjustableClock = adjustableClock, - FramedClock = offsetClock, + Breaks = beatmap.Breaks }, hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { @@ -188,13 +188,14 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor) + RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + new SkipOverlay(firstObjectTime) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, - Breaks = beatmap.Breaks - } + AdjustableClock = adjustableClock, + FramedClock = offsetClock, + }, } }, failOverlay = new FailOverlay diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index 5cc4b30950..b79c212ade 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -85,11 +85,13 @@ namespace osu.Game.Screens.Play if (currentSecond != previousSecond && songCurrentTime < songLength) { - timeCurrent.Text = TimeSpan.FromSeconds(currentSecond).ToString(songCurrentTime < 0 ? @"\-m\:ss" : @"m\:ss"); - timeLeft.Text = TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime).ToString(@"\-m\:ss"); + timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond)); + timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime)); previousSecond = currentSecond; } } + + private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}"; } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 236b1310e1..97f6371cb2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -88,17 +88,27 @@ namespace osu.Game.Screens.Select private void loadBeatmap() { + void updateState() + { + State = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + Info?.FadeOut(250); + Info?.Expire(); + } + + if (beatmap == null) + { + updateState(); + return; + } + LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value) { Shear = -Shear, Depth = Info?.Depth + 1 ?? 0, }, newInfo => { - State = beatmap == null ? Visibility.Hidden : Visibility.Visible; - - Info?.FadeOut(250); - Info?.Expire(); - + updateState(); Add(Info = newInfo); }); }