diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 4b95a6754e..162624da57 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -28,18 +28,20 @@ namespace osu.Game.Rulesets.Catch.Tests protected override IEnumerable CreateConvertValue(HitObject hitObject) { - if (hitObject is JuiceStream stream) + switch (hitObject) { - foreach (var nested in stream.NestedHitObjects) - yield return new ConvertValue((CatchHitObject)nested); + case JuiceStream stream: + foreach (var nested in stream.NestedHitObjects) + yield return new ConvertValue((CatchHitObject)nested); + break; + case BananaShower shower: + foreach (var nested in shower.NestedHitObjects) + yield return new ConvertValue((CatchHitObject)nested); + break; + default: + yield return new ConvertValue((CatchHitObject)hitObject); + break; } - else if (hitObject is BananaShower shower) - { - foreach (var nested in shower.NestedHitObjects) - yield return new ConvertValue((CatchHitObject)nested); - } - else - yield return new ConvertValue((CatchHitObject)hitObject); } protected override Ruleset CreateRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 31c56c37c4..a763989750 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -46,13 +46,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty foreach (var hitObject in beatmap.HitObjects) { - // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. - if (hitObject is Fruit) + switch (hitObject) { - difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth)); + // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. + case Fruit fruit: + difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth)); + break; + case JuiceStream _: + difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); + break; } - if (hitObject is JuiceStream) - difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); } difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 9eca8f6871..ea3b6fb0e0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -23,8 +23,10 @@ namespace osu.Game.Rulesets.Catch.UI private readonly CatcherArea catcherArea; public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) - : base(ScrollingDirection.Down, BASE_WIDTH) + : base(BASE_WIDTH) { + Direction.Value = ScrollingDirection.Down; + Container explodingFruitContainer; Anchor = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index de2bfaed9c..cceee718ca 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createColumn(ScrollingDirection direction, ManiaAction action) { - var column = new Column(direction) + var column = new Column { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs index 8046c46fc1..b1bb7f5187 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } }; + var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } }; stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 280c2f45d4..8d0d78120a 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -133,26 +133,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 6.5) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); } if (ConversionDifficulty > 4) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); } if (ConversionDifficulty > 2.5) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); } - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); } @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) { while (PreviousPattern.ColumnHasObject(nextColumn)) nextColumn = Random.Next(RandomStart, TotalColumns); @@ -361,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH; - bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; + bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); if (canGenerateTwoNotes) @@ -391,7 +391,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnRepeat = Math.Min(spanCount, TotalColumns); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) { while (PreviousPattern.ColumnHasObject(nextColumn)) nextColumn = Random.Next(RandomStart, TotalColumns); @@ -425,7 +425,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) { while (PreviousPattern.ColumnHasObject(holdColumn)) holdColumn = Random.Next(RandomStart, TotalColumns); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index b902ee63c6..06b4b8a27e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -33,15 +33,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy bool generateHold = endTime - HitObject.StartTime >= 100; - if (TotalColumns == 8) + switch (TotalColumns) { - if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) + case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: addToPattern(pattern, 0, generateHold); - else + break; + case 8: addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); + break; + default: + if (TotalColumns > 0) + addToPattern(pattern, getNextRandomColumn(0), generateHold); + break; } - else if (TotalColumns > 0) - addToPattern(pattern, getNextRandomColumn(0), generateHold); return pattern; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 0e839d87a2..84ebfdb839 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private readonly PatternType convertType; - public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, IBeatmap originalBeatmap) + public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, + PatternType lastStair, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); @@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy else convertType |= PatternType.LowProbability; - if ((convertType & PatternType.KeepSingle) == 0) + if (!convertType.HasFlag(PatternType.KeepSingle)) { if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8) convertType |= PatternType.Mirror; @@ -107,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; - if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) + if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any()) { // Generate a new pattern by copying the last hit objects in reverse-column order for (int i = RandomStart; i < TotalColumns; i++) @@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1 + if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 // If we convert to 7K + 1, let's not overload the special key && (TotalColumns != 8 || lastColumn != 0) // Make sure the last column was not the centre column @@ -130,7 +131,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any()) + if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any()) { // Generate a new pattern by placing on the already filled columns for (int i = RandomStart; i < TotalColumns; i++) @@ -142,7 +143,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (PreviousPattern.HitObjects.Count() == 1) { - if ((convertType & PatternType.Stair) > 0) + if (convertType.HasFlag(PatternType.Stair)) { // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" int targetColumn = lastColumn + 1; @@ -153,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if ((convertType & PatternType.ReverseStair) > 0) + if (convertType.HasFlag(PatternType.ReverseStair)) { // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" int targetColumn = lastColumn - 1; @@ -165,10 +166,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } } - if ((convertType & PatternType.KeepSingle) > 0) + if (convertType.HasFlag(PatternType.KeepSingle)) return pattern = generateRandomNotes(1); - if ((convertType & PatternType.Mirror) > 0) + if (convertType.HasFlag(PatternType.Mirror)) { if (ConversionDifficulty > 6.5) return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); @@ -179,21 +180,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 6.5) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.78, 0.42, 0, 0); return pattern = generateRandomPattern(1, 0.62, 0, 0); } if (ConversionDifficulty > 4) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.35, 0.08, 0, 0); return pattern = generateRandomPattern(0.52, 0.15, 0, 0); } if (ConversionDifficulty > 2) { - if ((convertType & PatternType.LowProbability) > 0) + if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.18, 0, 0, 0); return pattern = generateRandomPattern(0.45, 0, 0, 0); } @@ -204,9 +205,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { foreach (var obj in pattern.HitObjects) { - if ((convertType & PatternType.Stair) > 0 && obj.Column == TotalColumns - 1) + if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) StairType = PatternType.ReverseStair; - if ((convertType & PatternType.ReverseStair) > 0 && obj.Column == RandomStart) + if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) StairType = PatternType.Stair; } } @@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { var pattern = new Pattern(); - bool allowStacking = (convertType & PatternType.ForceNotStack) == 0; + bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack); if (!allowStacking) noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); @@ -235,7 +236,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) { - if ((convertType & PatternType.Gathered) > 0) + if (convertType.HasFlag(PatternType.Gathered)) { nextColumn++; if (nextColumn == TotalColumns) @@ -367,7 +368,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { addToCentre = false; - if ((convertType & PatternType.ForceNotStack) > 0) + if (convertType.HasFlag(PatternType.ForceNotStack)) return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); switch (TotalColumns) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index d006a3e1c7..0eef540d97 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -56,10 +56,15 @@ namespace osu.Game.Rulesets.Mania.Replays { foreach (var point in group) { - if (point is HitPoint) - actions.Add(columnActions[point.Column]); - if (point is ReleasePoint) - actions.Remove(columnActions[point.Column]); + switch (point) + { + case HitPoint _: + actions.Add(columnActions[point.Column]); + break; + case ReleasePoint _: + actions.Remove(columnActions[point.Column]); + break; + } } Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 8465258055..063531da45 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -32,8 +32,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override Container Content => hitObjectArea; - public Column(ScrollingDirection direction) - : base(direction) + public Column() { RelativeSizeAxes = Axes.Y; Width = column_width; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 2f8ad7b17e..d6781a6e8f 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { @@ -21,8 +20,7 @@ namespace osu.Game.Rulesets.Mania.UI public List Columns => stages.SelectMany(x => x.Columns).ToList(); private readonly List stages = new List(); - public ManiaPlayfield(ScrollingDirection direction, List stageDefinitions) - : base(direction) + public ManiaPlayfield(List stageDefinitions) { if (stageDefinitions == null) throw new ArgumentNullException(nameof(stageDefinitions)); @@ -42,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.UI int firstColumnIndex = 0; for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); newStage.VisibleTimeRange.BindTo(VisibleTimeRange); playfieldGrid.Content[0][i] = newStage; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index abc9705119..aaa4505b5e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI return dependencies; } - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages) + protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs index f1ff0665cd..4d6c5a747a 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs @@ -11,11 +11,6 @@ namespace osu.Game.Rulesets.Mania.UI { private readonly IBindable direction = new Bindable(); - public ManiaScrollingPlayfield(ScrollingDirection direction) - : base(direction) - { - } - [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 4c7deb4567..a0ff8780ad 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -43,8 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int firstColumnIndex; - public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) - : base(direction) + public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; @@ -124,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var isSpecial = definition.IsSpecialColumn(i); - var column = new Column(direction) + var column = new Column { IsSpecial = isSpecial, Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index ad4ea343d2..7322f65066 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -285,44 +285,45 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); - // We add intermediate frames for spinning / following a slider here. - if (h is Spinner) + switch (h) { - Spinner s = h as Spinner; - - Vector2 difference = startPosition - SPINNER_CENTRE; - - float radius = difference.Length; - float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); - - double t; - - for (double j = h.StartTime + FrameDelay; j < s.EndTime; j += FrameDelay) + // We add intermediate frames for spinning / following a slider here. + case Spinner spinner: { - t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; + Vector2 difference = startPosition - SPINNER_CENTRE; - Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); - AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); + float radius = difference.Length; + float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); + + double t; + + for (double j = h.StartTime + FrameDelay; j < spinner.EndTime; j += FrameDelay) + { + t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; + + Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); + AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); + } + + t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection; + Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); + + AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); + + endFrame.Position = endPosition; + break; } - - t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; - Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); - - AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); - - endFrame.Position = endPosition; - } - else if (h is Slider) - { - Slider s = h as Slider; - - for (double j = FrameDelay; j < s.Duration; j += FrameDelay) + case Slider slider: { - Vector2 pos = s.StackedPositionAt(j / s.Duration); - AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); - } + for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) + { + Vector2 pos = slider.StackedPositionAt(j / slider.Duration); + AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); + } - AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action)); + AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action)); + break; + } } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 35146dfe29..4d6722b61b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -39,6 +39,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private int downCount; + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + private float targetScale => downCount > 0 ? pressed_scale : released_scale; + public bool OnPressed(OsuAction action) { switch (action) @@ -46,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor case OsuAction.LeftButton: case OsuAction.RightButton: downCount++; - ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + ActiveCursor.ScaleTo(released_scale).ScaleTo(targetScale, 100, Easing.OutQuad); break; } @@ -60,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor case OsuAction.LeftButton: case OsuAction.RightButton: if (--downCount == 0) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + ActiveCursor.ScaleTo(targetScale, 200, Easing.OutQuad); break; } @@ -72,13 +77,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(targetScale, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(targetScale * 0.8f, 450, Easing.OutQuint); } public class OsuCursor : Container diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 33d4e16662..4bc6992445 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using OpenTK; @@ -33,19 +34,30 @@ namespace osu.Game.Rulesets.Osu.UI protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { - if (h is HitCircle circle) - return new DrawableHitCircle(circle); + switch (h) + { + case HitCircle circle: + return new DrawableHitCircle(circle); + case Slider slider: + return new DrawableSlider(slider); + case Spinner spinner: + return new DrawableSpinner(spinner); + } - if (h is Slider slider) - return new DrawableSlider(slider); - - if (h is Spinner spinner) - return new DrawableSpinner(spinner); return null; } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); + public override double GameplayStartTime + { + get + { + var first = (OsuHitObject)Objects.First(); + return first.StartTime - first.TimePreempt; + } + } + protected override Vector2 GetAspectAdjustedSize() { var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index c7f75e44fa..7dd50ab8b8 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -80,30 +80,31 @@ namespace osu.Game.Rulesets.Taiko.Scoring foreach (var obj in beatmap.HitObjects) { - if (obj is Hit) + switch (obj) { - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - if (obj.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - else if (obj is DrumRoll) - { - for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType().Count(); i++) - { - AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); + case Hit _: + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + if (obj.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + break; + case DrumRoll drumRoll: + var count = drumRoll.NestedHitObjects.OfType().Count(); + for (int i = 0; i < count; i++) + { + AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); + + if (obj.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); if (obj.IsStrong) AddJudgement(new TaikoStrongHitJudgement()); - } - - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - - if (obj.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - else if (obj is Swell) - { - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + break; + case Swell _: + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + break; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7fdd3cd1e2..bb5994c2f2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -58,8 +58,9 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Box background; public TaikoPlayfield(ControlPointInfo controlPoints) - : base(ScrollingDirection.Left) { + Direction.Value = ScrollingDirection.Left; + AddRangeInternal(new Drawable[] { backgroundContainer = new Container @@ -212,13 +213,15 @@ namespace osu.Game.Rulesets.Taiko.UI base.Add(h); - var barline = h as DrawableBarLine; - if (barline != null) - barlineContainer.Add(barline.CreateProxy()); - - var taikoObject = h as DrawableTaikoHitObject; - if (taikoObject != null) - topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); + switch (h) + { + case DrawableBarLine barline: + barlineContainer.Add(barline.CreateProxy()); + break; + case DrawableTaikoHitObject taikoObject: + topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); + break; + } } internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index e04b4d45f6..2fa4627bde 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -98,32 +98,22 @@ namespace osu.Game.Rulesets.Taiko.UI protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) { - var centreHit = h as CentreHit; - if (centreHit != null) + switch (h) { - if (h.IsStrong) + case CentreHit centreHit when h.IsStrong: return new DrawableCentreHitStrong(centreHit); - return new DrawableCentreHit(centreHit); - } - - var rimHit = h as RimHit; - if (rimHit != null) - { - if (h.IsStrong) + case CentreHit centreHit: + return new DrawableCentreHit(centreHit); + case RimHit rimHit when h.IsStrong: return new DrawableRimHitStrong(rimHit); - return new DrawableRimHit(rimHit); + case RimHit rimHit: + return new DrawableRimHit(rimHit); + case DrumRoll drumRoll: + return new DrawableDrumRoll(drumRoll); + case Swell swell: + return new DrawableSwell(swell); } - var drumRoll = h as DrumRoll; - if (drumRoll != null) - { - return new DrawableDrumRoll(drumRoll); - } - - var swell = h as Swell; - if (swell != null) - return new DrawableSwell(swell); - return null; } diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/TestCaseIconButton.cs index 16363da527..14cba71ec8 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/TestCaseIconButton.cs @@ -25,11 +25,7 @@ namespace osu.Game.Tests.Visual Children = new[] { new NamedIconButton("No change", new IconButton()), - new NamedIconButton("Background colours", new IconButton - { - FlashColour = Color4.DarkGreen, - HoverColour = Color4.Green, - }), + new NamedIconButton("Background colours", new ColouredIconButton()), new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), new NamedIconButton("Unchanging size", new IconButton(), false), new NamedIconButton("Icon colours", new IconButton @@ -41,6 +37,15 @@ namespace osu.Game.Tests.Visual }; } + private class ColouredIconButton : IconButton + { + public ColouredIconButton() + { + FlashColour = Color4.DarkGreen; + HoverColour = Color4.Green; + } + } + private class NamedIconButton : Container { public NamedIconButton(string name, IconButton button, bool allowSizeChange = true) diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index a20f67e336..b98875cd6a 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -18,7 +18,6 @@ namespace osu.Game.Tests.Visual { Origin = Anchor.Centre, Anchor = Anchor.Centre, - IsCounting = true, Children = new KeyCounter[] { new KeyCounterKeyboard(Key.Z), diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index dab7f7e037..b94fb42bf0 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -using osu.Game.Tests.Platform; namespace osu.Game.Tests.Visual { @@ -28,6 +27,7 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; private WorkingBeatmap defaultBeatmap; + private DatabaseContextFactory factory; public override IReadOnlyList RequiredTypes => new[] { @@ -59,13 +59,14 @@ namespace osu.Game.Tests.Visual { TestSongSelect songSelect = null; - var storage = new TestStorage(@"TestCasePlaySongSelect"); + factory = new DatabaseContextFactory(LocalStorage); + factory.ResetDatabase(); - // this is by no means clean. should be replacing inside of OsuGameBase somehow. - IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); + using (var usage = factory.Get()) + usage.Migrate(); Dependencies.Cache(rulesets = new RulesetStore(factory)); - Dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null) { DefaultBeatmap = defaultBeatmap = Beatmap.Default }); diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index 9b48ec17bd..bbc9d2b860 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -117,7 +117,6 @@ namespace osu.Game.Tests.Visual public new readonly ScrollingDirection Direction; public TestPlayfield(ScrollingDirection direction) - : base(direction) { Direction = direction; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fc4d43080e..67f02c8ac4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -45,6 +45,16 @@ namespace osu.Game.Beatmaps /// public event Action BeatmapDownloadBegan; + /// + /// Fired when a beatmap download is interrupted, due to user cancellation or other failures. + /// + public event Action BeatmapDownloadFailed; + + /// + /// Fired when a beatmap load is requested (into the interactive game UI). + /// + public Action PresentBeatmap; + /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// @@ -79,33 +89,46 @@ namespace osu.Game.Beatmaps this.audioManager = audioManager; } - protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) + protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive) { - model.Beatmaps = createBeatmapDifficulties(archive); + if (archive != null) + beatmapSet.Beatmaps = createBeatmapDifficulties(archive); - foreach (BeatmapInfo b in model.Beatmaps) + foreach (BeatmapInfo b in beatmapSet.Beatmaps) { // remove metadata from difficulties where it matches the set - if (model.Metadata.Equals(b.Metadata)) + if (beatmapSet.Metadata.Equals(b.Metadata)) b.Metadata = null; - // by setting the model here, we can update the noline set id below. - b.BeatmapSet = model; - - fetchAndPopulateOnlineIDs(b, model.Beatmaps); + b.BeatmapSet = beatmapSet; } // check if a set already exists with the same online id, delete if it does. - if (model.OnlineBeatmapSetID != null) + if (beatmapSet.OnlineBeatmapSetID != null) { - var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); if (existingOnlineId != null) { Delete(existingOnlineId); beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); + Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); } } + + validateOnlineIds(beatmapSet.Beatmaps); + + foreach (BeatmapInfo b in beatmapSet.Beatmaps) + fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps); + } + + private void validateOnlineIds(List beatmaps) + { + var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + + // ensure all IDs are unique in this set and none match existing IDs in the local beatmap store. + if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any()) + // remove all online IDs if any problems were found. + beatmaps.ForEach(b => b.OnlineBeatmapID = null); } protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) @@ -143,7 +166,7 @@ namespace osu.Game.Beatmaps return; } - var downloadNotification = new ProgressNotification + var downloadNotification = new DownloadNotification { CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", @@ -163,18 +186,28 @@ namespace osu.Game.Beatmaps Task.Factory.StartNew(() => { + BeatmapSetInfo importedBeatmap; + // This gets scheduled back to the update thread, but we want the import to run in the background. using (var stream = new MemoryStream(data)) using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) - Import(archive); + importedBeatmap = Import(archive); + downloadNotification.CompletionClickAction = () => + { + PresentBeatmap?.Invoke(importedBeatmap); + return true; + }; downloadNotification.State = ProgressNotificationState.Completed; + currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += error => { + BeatmapDownloadFailed?.Invoke(request); + if (error is OperationCanceledException) return; downloadNotification.State = ProgressNotificationState.Cancelled; @@ -276,7 +309,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public IEnumerable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); + public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); /// /// Denotes whether an osu-stable installation is present to perform automated imports from. @@ -339,8 +372,6 @@ namespace osu.Game.Beatmaps { var beatmapInfos = new List(); - bool invalidateOnlineIDs = false; - foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu"))) { using (var raw = reader.GetStream(name)) @@ -357,38 +388,15 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); - if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue) - { - var ourId = beatmap.BeatmapInfo.OnlineBeatmapID; - - // check that no existing beatmap in database exists that is imported with the same online beatmap ID. if so, give it precedence. - if (QueryBeatmap(b => b.OnlineBeatmapID.Value == ourId) != null) - beatmap.BeatmapInfo.OnlineBeatmapID = null; - - // check that no other beatmap in this imported set has a conflicting online beatmap ID. If so, presume *all* are incorrect. - if (beatmapInfos.Any(b => b.OnlineBeatmapID == ourId)) - invalidateOnlineIDs = true; - } - - RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); - + var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); beatmap.BeatmapInfo.Ruleset = ruleset; - - if (ruleset != null) - { - // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating; - } - else - beatmap.BeatmapInfo.StarDifficulty = 0; + // TODO: this should be done in a better place once we actually need to dynamically update it. + beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmapInfos.Add(beatmap.BeatmapInfo); } } - if (invalidateOnlineIDs) - beatmapInfos.ForEach(b => b.OnlineBeatmapID = null); - return beatmapInfos; } @@ -401,12 +409,12 @@ namespace osu.Game.Beatmaps /// True if population was successful. private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false) { + if (api?.State != APIState.Online) + return false; + if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null) return true; - if (api.State != APIState.Online) - return false; - Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database); try @@ -453,5 +461,21 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => null; protected override Track GetTrack() => null; } + + private class DownloadNotification : ProgressNotification + { + public override bool IsImportant => false; + + protected override Notification CreateCompletionNotification() => new SilencedProgressCompletionNotification + { + Activated = CompletionClickAction, + Text = CompletionText + }; + + private class SilencedProgressCompletionNotification : ProgressCompletionNotification + { + public override bool IsImportant => false; + } + } } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs index 6acb58e165..6f4d4c0d6f 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; namespace osu.Game.Beatmaps.Drawables { @@ -19,9 +20,9 @@ namespace osu.Game.Beatmaps.Drawables private BeatmapManager beatmaps; /// - /// Whether the associated beatmap set has been downloading (by this instance or any other instance). + /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded. /// - public readonly BindableBool Downloaded = new BindableBool(); + public readonly Bindable DownloadState = new Bindable(); public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false) { @@ -36,10 +37,16 @@ namespace osu.Game.Beatmaps.Drawables beatmaps.ItemAdded += setAdded; beatmaps.ItemRemoved += setRemoved; + beatmaps.BeatmapDownloadBegan += downloadBegan; + beatmaps.BeatmapDownloadFailed += downloadFailed; // initial value - if (set.OnlineBeatmapSetID != null) - Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any(); + if (set.OnlineBeatmapSetID != null && beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any()) + DownloadState.Value = DownloadStatus.Downloaded; + else if (beatmaps.GetExistingDownload(set) != null) + DownloadState.Value = DownloadStatus.Downloading; + else + DownloadState.Value = DownloadStatus.NotDownloaded; } protected override void Dispose(bool isDisposing) @@ -50,6 +57,8 @@ namespace osu.Game.Beatmaps.Drawables { beatmaps.ItemAdded -= setAdded; beatmaps.ItemRemoved -= setRemoved; + beatmaps.BeatmapDownloadBegan -= downloadBegan; + beatmaps.BeatmapDownloadFailed -= downloadFailed; } } @@ -57,28 +66,45 @@ namespace osu.Game.Beatmaps.Drawables /// Begin downloading the associated beatmap set. /// /// True if downloading began. False if an existing download is active or completed. - public bool Download() + public void Download() { - if (Downloaded.Value) - return false; - - if (beatmaps.GetExistingDownload(set) != null) - return false; + if (DownloadState.Value > DownloadStatus.NotDownloaded) + return; beatmaps.Download(set, noVideo); - return true; + + DownloadState.Value = DownloadStatus.Downloading; } private void setAdded(BeatmapSetInfo s) { if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - Downloaded.Value = true; + DownloadState.Value = DownloadStatus.Downloaded; } private void setRemoved(BeatmapSetInfo s) { if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - Downloaded.Value = false; + DownloadState.Value = DownloadStatus.NotDownloaded; + } + + private void downloadBegan(DownloadBeatmapSetRequest d) + { + if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + DownloadState.Value = DownloadStatus.Downloading; + } + + private void downloadFailed(DownloadBeatmapSetRequest d) + { + if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + DownloadState.Value = DownloadStatus.NotDownloaded; + } + + public enum DownloadStatus + { + NotDownloaded, + Downloading, + Downloaded, } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c79938e613..52d9f31814 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -62,28 +62,30 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseLine(Beatmap beatmap, Section section, string line) { + var strippedLine = StripComments(line); + switch (section) { case Section.General: - handleGeneral(line); + handleGeneral(strippedLine); return; case Section.Editor: - handleEditor(line); + handleEditor(strippedLine); return; case Section.Metadata: handleMetadata(line); return; case Section.Difficulty: - handleDifficulty(line); + handleDifficulty(strippedLine); return; case Section.Events: - handleEvent(line); + handleEvent(strippedLine); return; case Section.TimingPoints: - handleTimingPoint(line); + handleTimingPoint(strippedLine); return; case Section.HitObjects: - handleHitObject(line); + handleHitObject(strippedLine); return; } @@ -305,9 +307,9 @@ namespace osu.Game.Beatmaps.Formats bool omitFirstBarSignature = false; if (split.Length >= 8) { - int effectFlags = int.Parse(split[7]); - kiaiMode = (effectFlags & 1) > 0; - omitFirstBarSignature = (effectFlags & 8) > 0; + EffectFlags effectFlags = (EffectFlags)int.Parse(split[7]); + kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); + omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } string stringSampleSet = sampleSet.ToString().ToLower(); @@ -405,5 +407,13 @@ namespace osu.Game.Beatmaps.Formats private double getOffsetTime() => ApplyOffsets ? offset : 0; private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); + + [Flags] + internal enum EffectFlags + { + None = 0, + Kiai = 1, + OmitFirstBarLine = 8 + } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index c8874c3bc7..91c1c98438 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -57,6 +57,8 @@ namespace osu.Game.Beatmaps.Formats protected virtual void ParseLine(T output, Section section, string line) { + line = StripComments(line); + switch (section) { case Section.Colours: @@ -65,6 +67,14 @@ namespace osu.Game.Beatmaps.Formats } } + protected string StripComments(string line) + { + var index = line.IndexOf("//", StringComparison.Ordinal); + if (index > 0) + return line.Substring(0, index); + return line; + } + private bool hasComboColours; private void handleColours(T output, string line) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 868ae5206a..b418cbd5ec 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -42,6 +42,8 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseLine(Storyboard storyboard, Section section, string line) { + line = StripComments(line); + switch (section) { case Section.Events: diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index cbf0df3227..0465c0ad73 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.IO.File; using osu.Framework.Logging; @@ -175,7 +176,24 @@ namespace osu.Game.Database /// The archive to be imported. public TModel Import(ArchiveReader archive) { - TModel item = null; + try + { + return Import(CreateModel(archive), archive); + } + catch (Exception e) + { + Logger.Error(e, $"Model creation of {archive.Name} failed.", LoggingTarget.Database); + return null; + } + } + + /// + /// Import an item from a . + /// + /// The model to be imported. + /// An optional archive to use for model population. + public TModel Import(TModel item, ArchiveReader archive = null) + { delayEvents(); try @@ -186,18 +204,16 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - // create a new model (don't yet add to database) - item = CreateModel(archive); - var existing = CheckForExisting(item); if (existing != null) { - Logger.Log($"Found existing {typeof(TModel)} for {archive.Name} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); return existing; } - item.Files = createFileInfos(archive, Files); + if (archive != null) + item.Files = createFileInfos(archive, Files); Populate(item, archive); @@ -211,11 +227,11 @@ namespace osu.Game.Database } } - Logger.Log($"Import of {archive.Name} successfully completed!", LoggingTarget.Database); + Logger.Log($"Import of {item} successfully completed!", LoggingTarget.Database); } catch (Exception e) { - Logger.Error(e, $"Import of {archive.Name} failed and has been rolled back.", LoggingTarget.Database); + Logger.Error(e, $"Import of {item} failed and has been rolled back.", LoggingTarget.Database); item = null; } finally @@ -227,12 +243,6 @@ namespace osu.Game.Database return item; } - /// - /// Import an item from a . - /// - /// The model to be imported. - public void Import(TModel item) => ModelStore.Add(item); - /// /// Perform an update of the specified item. /// TODO: Support file changes. @@ -385,8 +395,8 @@ namespace osu.Game.Database /// After this method, the model should be in a state ready to commit to a store. /// /// The model to populate. - /// The archive to use as a reference for population. - protected virtual void Populate(TModel model, ArchiveReader archive) + /// The archive to use as a reference for population. May be null. + protected virtual void Populate(TModel model, [CanBeNull] ArchiveReader archive) { } diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 5160239c38..c20d4569f6 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -11,7 +11,7 @@ namespace osu.Game.Database { public class DatabaseContextFactory : IDatabaseContextFactory { - private readonly GameHost host; + private readonly Storage storage; private const string database_name = @"client"; @@ -26,9 +26,9 @@ namespace osu.Game.Database private IDbContextTransaction currentWriteTransaction; - public DatabaseContextFactory(GameHost host) + public DatabaseContextFactory(Storage storage) { - this.host = host; + this.storage = storage; recycleThreadContexts(); } @@ -117,7 +117,7 @@ namespace osu.Game.Database private void recycleThreadContexts() => threadContexts = new ThreadLocal(CreateContext); - protected virtual OsuDbContext CreateContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)) + protected virtual OsuDbContext CreateContext() => new OsuDbContext(storage.GetDatabaseConnectionString(database_name)) { Database = { AutoTransactionsEnabled = false } }; @@ -129,7 +129,7 @@ namespace osu.Game.Database recycleThreadContexts(); GC.Collect(); GC.WaitForPendingFinalizers(); - host.Storage.DeleteDatabase(database_name); + storage.DeleteDatabase(database_name); } } } diff --git a/osu.Game/Database/SingletonContextFactory.cs b/osu.Game/Database/SingletonContextFactory.cs deleted file mode 100644 index ce3fbf6881..0000000000 --- a/osu.Game/Database/SingletonContextFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - public class SingletonContextFactory : IDatabaseContextFactory - { - private readonly OsuDbContext context; - - public SingletonContextFactory(OsuDbContext context) - { - this.context = context; - } - - public OsuDbContext Get() => context; - - public DatabaseWriteUsage GetForWrite(bool withTransaction = true) => new DatabaseWriteUsage(context, null); - } -} diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index d065ecdd5f..0ba49929e9 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -26,10 +26,10 @@ namespace osu.Game.Graphics.UserInterface set { direction = value; - base.Direction = (direction & BarDirection.Horizontal) > 0 ? FillDirection.Vertical : FillDirection.Horizontal; + base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; foreach (var bar in Children) { - bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); + bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); bar.Direction = direction; } } @@ -56,14 +56,14 @@ namespace osu.Game.Graphics.UserInterface if (bar.Bar != null) { bar.Bar.Length = length; - bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1); + bar.Bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1); } else { Add(new Bar { RelativeSizeAxes = Axes.Both, - Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1), + Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1), Length = length, Direction = Direction, }); diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 5a25fe641d..eca7bf57c8 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -3,31 +3,17 @@ using OpenTK; using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input; -using osu.Game.Graphics.Containers; namespace osu.Game.Graphics.UserInterface { - public class IconButton : OsuClickableContainer + public class IconButton : OsuAnimatedButton { public const float BUTTON_SIZE = 30; - private Color4? flashColour; - /// - /// The colour that should be flashed when the is clicked. - /// - public Color4 FlashColour - { - get { return flashColour ?? Color4.White; } - set { flashColour = value; } - } - private Color4? iconColour; + /// /// The icon colour. This does not affect . /// @@ -42,6 +28,7 @@ namespace osu.Game.Graphics.UserInterface } private Color4? iconHoverColour; + /// /// The icon colour while the is hovered. /// @@ -51,20 +38,6 @@ namespace osu.Game.Graphics.UserInterface set { iconHoverColour = value; } } - private Color4? hoverColour; - /// - /// The background colour of the while it is hovered. - /// - public Color4 HoverColour - { - get { return hoverColour ?? Color4.White; } - set - { - hoverColour = value; - hover.Colour = value; - } - } - /// /// The icon. /// @@ -88,93 +61,39 @@ namespace osu.Game.Graphics.UserInterface /// public Vector2 ButtonSize { - get { return content.Size; } - set { content.Size = value; } + get => Content.Size; + set + { + Content.RelativeSizeAxes = Axes.None; + Content.Size = value; + } } - private readonly Container content; private readonly SpriteIcon icon; - private readonly Box hover; public IconButton() { AutoSizeAxes = Axes.Both; + ButtonSize = new Vector2(BUTTON_SIZE); - Children = new Drawable[] + Add(icon = new SpriteIcon { - content = new Container - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(BUTTON_SIZE), - CornerRadius = 5, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.04f), - Type = EdgeEffectType.Shadow, - Radius = 5, - }, - Children = new Drawable[] - { - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - icon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(18), - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (hoverColour == null) - HoverColour = colours.Yellow.Opacity(0.6f); - - if (flashColour == null) - FlashColour = colours.Yellow; - - Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint); + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(18), + }); } protected override bool OnHover(InputState state) { - hover.FadeIn(500, Easing.OutQuint); icon.FadeColour(IconHoverColour, 500, Easing.OutQuint); return base.OnHover(state); } protected override void OnHoverLost(InputState state) { - hover.FadeOut(500, Easing.OutQuint); icon.FadeColour(IconColour, 500, Easing.OutQuint); base.OnHoverLost(state); } - - protected override bool OnClick(InputState state) - { - hover.FlashColour(FlashColour, 800, Easing.OutQuint); - return base.OnClick(state); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - content.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); - } } } diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs new file mode 100644 index 0000000000..990a2f20a9 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics.Containers; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Highlight on hover, bounce on click. + /// + public class OsuAnimatedButton : OsuClickableContainer + { + /// + /// The colour that should be flashed when the is clicked. + /// + protected Color4 FlashColour = Color4.White.Opacity(0.3f); + + private Color4 hoverColour = Color4.White.Opacity(0.1f); + + /// + /// The background colour of the while it is hovered. + /// + protected Color4 HoverColour + { + get => hoverColour; + set + { + hoverColour = value; + hover.Colour = value; + } + } + + protected override Container Content => content; + + private readonly Container content; + private readonly Box hover; + + public OsuAnimatedButton() + { + base.Content.Add(content = new Container + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.04f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }, + Children = new Drawable[] + { + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = HoverColour, + Blending = BlendingMode.Additive, + Alpha = 0, + }, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Enabled.BindValueChanged(enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true); + } + + protected override bool OnHover(InputState state) + { + hover.FadeIn(500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + hover.FadeOut(500, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + hover.FlashColour(FlashColour, 800, Easing.OutQuint); + return base.OnClick(state); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + Content.ScaleTo(0.75f, 2000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + Content.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index f490306acf..dd5454314d 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -56,15 +56,15 @@ namespace osu.Game.Graphics.UserInterface set { base.Origin = value; - c1.Origin = c1.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopLeft : Anchor.TopRight; - c2.Origin = c2.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopRight : Anchor.TopLeft; + c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; + c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; - X = (value & Anchor.x2) > 0 ? SIZE_RETRACTED.X * shear * 0.5f : 0; + X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear * 0.5f : 0; Remove(c1); Remove(c2); - c1.Depth = (value & Anchor.x2) > 0 ? 0 : 1; - c2.Depth = (value & Anchor.x2) > 0 ? 1 : 0; + c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1; + c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0; Add(c1); Add(c2); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 557b6e4469..18a1d018d0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -24,6 +24,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Notifications; @@ -34,6 +35,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using OpenTK.Graphics; using osu.Game.Overlays.Volume; +using osu.Game.Screens.Select; namespace osu.Game { @@ -179,6 +181,41 @@ namespace osu.Game /// The set to display. public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + /// + /// Present a beatmap at song select. + /// + /// The beatmap to select. + public void PresentBeatmap(BeatmapSetInfo beatmap) + { + CloseAllOverlays(false); + + void setBeatmap() + { + if (Beatmap.Disabled) + { + Schedule(setBeatmap); + return; + } + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(beatmap.Beatmaps.First()); + } + + switch (currentScreen) + { + case SongSelect _: + break; + default: + // navigate to song select if we are not already there. + var menu = (MainMenu)intro.ChildScreen; + + menu.MakeCurrent(); + menu.LoadToSolo(); + break; + } + + setBeatmap(); + } + /// /// Show a user's profile as an overlay. /// @@ -245,6 +282,7 @@ namespace osu.Game BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; + BeatmapManager.PresentBeatmap = PresentBeatmap; AddRange(new Drawable[] { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a9b74d6740..63cc883844 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -107,7 +107,7 @@ namespace osu.Game { Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll")); - dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); + dependencies.Cache(contextFactory = new DatabaseContextFactory(Host.Storage)); dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 4fce6a49fb..223ca1c904 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -61,21 +61,29 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Action = () => { - if (!downloader.Download()) + if (downloader.DownloadState.Value == BeatmapSetDownloader.DownloadStatus.Downloading) { Content.MoveToX(-5, 50, Easing.OutSine).Then() .MoveToX(5, 100, Easing.InOutSine).Then() .MoveToX(-5, 100, Easing.InOutSine).Then() .MoveToX(0, 50, Easing.InSine); + return; } + + downloader.Download(); }; - downloader.Downloaded.ValueChanged += d => + downloader.DownloadState.ValueChanged += state => { - if (d) - this.FadeOut(200); - else - this.FadeIn(200); + switch (state) + { + case BeatmapSetDownloader.DownloadStatus.Downloaded: + this.FadeOut(200); + break; + case BeatmapSetDownloader.DownloadStatus.NotDownloaded: + this.FadeIn(200); + break; + } }; } } diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index e286837746..c9e6f39270 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Direct public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { - Width = 400; + Width = 380; Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) } @@ -168,11 +168,10 @@ namespace osu.Game.Overlays.Direct }, new DownloadButton(SetInfo) { - Size = new Vector2(30), + Size = new Vector2(50, 30), Margin = new MarginPadding(horizontal_padding), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Colour = colours.Gray5, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 812a0e2073..17c5153dab 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -120,8 +120,8 @@ namespace osu.Game.Overlays.Direct Alpha = 0, Child = new DownloadButton(SetInfo) { - Size = new Vector2(height - vertical_padding * 2), - Margin = new MarginPadding { Left = vertical_padding }, + Size = new Vector2(height - vertical_padding * 3), + Margin = new MarginPadding { Left = vertical_padding, Right = vertical_padding }, }, }, new FillFlowContainer diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index e63c290ce5..76da4b61b0 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -99,6 +99,7 @@ namespace osu.Game.Overlays.Direct attachDownload(downloadRequest); beatmaps.BeatmapDownloadBegan += attachDownload; + beatmaps.ItemAdded += setAdded; } public override bool DisposeOnDeathRemoval => true; @@ -107,6 +108,7 @@ namespace osu.Game.Overlays.Direct { base.Dispose(isDisposing); beatmaps.BeatmapDownloadBegan -= attachDownload; + beatmaps.ItemAdded -= setAdded; } protected override void Update() @@ -171,6 +173,12 @@ namespace osu.Game.Overlays.Direct }; } + private void setAdded(BeatmapSetInfo s) + { + if (s.OnlineBeatmapSetID == SetInfo.OnlineBeatmapSetID) + progressBar.FadeOut(500); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 1ffa8dbd35..7758e171c5 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -1,76 +1,109 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using OpenTK; namespace osu.Game.Overlays.Direct { - public class DownloadButton : OsuClickableContainer + public class DownloadButton : OsuAnimatedButton { private readonly SpriteIcon icon; + private readonly SpriteIcon checkmark; + private readonly BeatmapSetDownloader downloader; + private readonly Box background; + + private OsuColour colours; public DownloadButton(BeatmapSetInfo set, bool noVideo = false) { - BeatmapSetDownloader downloader; - Children = new Drawable[] + AddRange(new Drawable[] { downloader = new BeatmapSetDownloader(set, noVideo), + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(30), - Icon = FontAwesome.fa_osu_chevron_down_o, + Size = new Vector2(13), + Icon = FontAwesome.fa_download, }, - }; + checkmark = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = 8, + Size = Vector2.Zero, + Icon = FontAwesome.fa_check, + } + }); Action = () => { - if (!downloader.Download()) + if (downloader.DownloadState == BeatmapSetDownloader.DownloadStatus.Downloading) { + // todo: replace with ShakeContainer after https://github.com/ppy/osu/pull/2909 is merged. Content.MoveToX(-5, 50, Easing.OutSine).Then() .MoveToX(5, 100, Easing.InOutSine).Then() .MoveToX(-5, 100, Easing.InOutSine).Then() .MoveToX(0, 50, Easing.InSine); } - }; - - downloader.Downloaded.ValueChanged += d => - { - if (d) - this.FadeOut(200); + else if (downloader.DownloadState == BeatmapSetDownloader.DownloadStatus.Downloaded) + { + // TODO: Jump to song select with this set when the capability is implemented + } else - this.FadeIn(200); + { + downloader.Download(); + } }; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override void LoadComplete() { - icon.ScaleTo(0.9f, 1000, Easing.Out); - return base.OnMouseDown(state, args); + base.LoadComplete(); + downloader.DownloadState.BindValueChanged(updateState, true); + FinishTransforms(true); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours) { - icon.ScaleTo(1f, 500, Easing.OutElastic); - return base.OnMouseUp(state, args); + this.colours = colours; } - protected override bool OnHover(InputState state) + private void updateState(BeatmapSetDownloader.DownloadStatus state) { - icon.ScaleTo(1.1f, 500, Easing.OutElastic); - return base.OnHover(state); - } + switch (state) + { + case BeatmapSetDownloader.DownloadStatus.NotDownloaded: + background.FadeColour(colours.Gray4, 500, Easing.InOutExpo); + icon.MoveToX(0, 500, Easing.InOutExpo); + checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); + break; - protected override void OnHoverLost(InputState state) - { - icon.ScaleTo(1f, 500, Easing.OutElastic); + case BeatmapSetDownloader.DownloadStatus.Downloading: + background.FadeColour(colours.Blue, 500, Easing.InOutExpo); + icon.MoveToX(0, 500, Easing.InOutExpo); + checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); + break; + + case BeatmapSetDownloader.DownloadStatus.Downloaded: + background.FadeColour(colours.Green, 500, Easing.InOutExpo); + icon.MoveToX(-8, 500, Easing.InOutExpo); + checkmark.ScaleTo(new Vector2(13), 500, Easing.InOutExpo); + break; + } } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c584a32a82..98cf111ba0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,6 +44,8 @@ namespace osu.Game.Overlays.Mods private void rulesetChanged(RulesetInfo newRuleset) { + if (newRuleset == null) return; + var instance = newRuleset.CreateInstance(); foreach (ModSection section in ModSectionsContainer.Children) @@ -173,7 +175,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + private void refreshSelectedMods() + { + SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + } public ModSelectOverlay() { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a57d5fd183..56af04c498 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -142,14 +142,14 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Children = new[] { - prevButton = new IconButton + prevButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = prev, Icon = FontAwesome.fa_step_backward, }, - playButton = new IconButton + playButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -158,7 +158,7 @@ namespace osu.Game.Overlays Action = play, Icon = FontAwesome.fa_play_circle_o, }, - nextButton = new IconButton + nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -167,7 +167,7 @@ namespace osu.Game.Overlays }, } }, - playlistButton = new IconButton + playlistButton = new MusicIconButton { Origin = Anchor.Centre, Anchor = Anchor.CentreRight, @@ -405,6 +405,16 @@ namespace osu.Game.Overlays Prev } + private class MusicIconButton : IconButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.YellowDark.Opacity(0.6f); + FlashColour = colours.Yellow; + } + } + private class Background : BufferedContainer { private readonly Sprite sprite; diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 3dc8f5ec15..d891cd96e8 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -128,7 +128,8 @@ namespace osu.Game.Overlays var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType))); section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); - State = Visibility.Visible; + if (notification.IsImportant) + State = Visibility.Visible; updateCounts(); }); diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index d2b5ae1829..3a3fcb54e0 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -23,6 +23,11 @@ namespace osu.Game.Overlays.Notifications /// public event Action Closed; + /// + /// Whether this notification should forcefully display itself. + /// + public virtual bool IsImportant => true; + /// /// Run on user activating the notification. Return true to close. /// diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 753cd33cc6..5a5b90ee25 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -188,16 +188,17 @@ namespace osu.Game.Overlays int optionCount = 0; int selectedOption = -1; - if (description.RawValue is bool) + switch (description.RawValue) { - optionCount = 1; - if ((bool)description.RawValue) selectedOption = 0; - } - else if (description.RawValue is Enum) - { - var values = Enum.GetValues(description.RawValue.GetType()); - optionCount = values.Length; - selectedOption = Convert.ToInt32(description.RawValue); + case bool val: + optionCount = 1; + if (val) selectedOption = 0; + break; + case Enum _: + var values = Enum.GetValues(description.RawValue.GetType()); + optionCount = values.Length; + selectedOption = Convert.ToInt32(description.RawValue); + break; } textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index c7870a72de..4514f3c33c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -118,9 +118,9 @@ namespace osu.Game.Overlays.Toolbar { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.Both, //stops us being considered in parent's autosize - Anchor = (TooltipAnchor & Anchor.x0) > 0 ? Anchor.BottomLeft : Anchor.BottomRight, + Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight, Origin = TooltipAnchor, - Position = new Vector2((TooltipAnchor & Anchor.x0) > 0 ? 5 : -5, 5), + Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5), Alpha = 0, Children = new[] { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 8f9651ab09..0de0a620e3 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -62,15 +62,19 @@ namespace osu.Game.Rulesets.Difficulty IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { - // Initial-case: Empty current set - if (currentSetCount == 0) - yield return new NoModMod(); - - if (currentSetCount == 1) - yield return currentSet.Single(); - - if (currentSetCount > 1) - yield return new MultiMod(currentSet.ToArray()); + switch (currentSetCount) + { + case 0: + // Initial-case: Empty current set + yield return new NoModMod(); + break; + case 1: + yield return currentSet.Single(); + break; + default: + yield return new MultiMod(currentSet.ToArray()); + break; + } // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod // combinations in further recursions, so a moving subset is used to eliminate this effect diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 7058d1bed6..8f73ff4c2d 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { - public class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer where T : HitObject { protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 95589d8953..dc1cd5ed3b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] split = text.Split(','); + Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); + ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; @@ -39,17 +41,15 @@ namespace osu.Game.Rulesets.Objects.Legacy HitObject result = null; - if ((type & ConvertHitObjectType.Circle) > 0) + if (type.HasFlag(ConvertHitObjectType.Circle)) { - result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); + result = CreateHit(pos, combo); if (split.Length > 5) readCustomSampleBanks(split[5], bankInfo); } - else if ((type & ConvertHitObjectType.Slider) > 0) + else if (type.HasFlag(ConvertHitObjectType.Slider)) { - var pos = new Vector2(int.Parse(split[0]), int.Parse(split[1])); - CurveType curveType = CurveType.Catmull; double length = 0; var points = new List { Vector2.Zero }; @@ -150,14 +150,14 @@ namespace osu.Game.Rulesets.Objects.Legacy result = CreateSlider(pos, combo, points, length, curveType, repeatCount, nodeSamples); } - else if ((type & ConvertHitObjectType.Spinner) > 0) + else if (type.HasFlag(ConvertHitObjectType.Spinner)) { result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + offset); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); } - else if ((type & ConvertHitObjectType.Hold) > 0) + else if (type.HasFlag(ConvertHitObjectType.Hold)) { // Note: Hold is generated by BMS converts @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime + offset); + result = CreateHold(pos, combo, endTime + offset); } if (result == null) @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } }; - if ((type & LegacySoundType.Finish) > 0) + if (type.HasFlag(LegacySoundType.Finish)) { soundTypes.Add(new SampleInfo { @@ -276,7 +276,7 @@ namespace osu.Game.Rulesets.Objects.Legacy }); } - if ((type & LegacySoundType.Whistle) > 0) + if (type.HasFlag(LegacySoundType.Whistle)) { soundTypes.Add(new SampleInfo { @@ -286,7 +286,7 @@ namespace osu.Game.Rulesets.Objects.Legacy }); } - if ((type & LegacySoundType.Clap) > 0) + if (type.HasFlag(LegacySoundType.Clap)) { soundTypes.Add(new SampleInfo { diff --git a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs index d39d765bfe..5bd56e0cc0 100644 --- a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs @@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Replays.Legacy public bool MouseLeft => MouseLeft1 || MouseLeft2; public bool MouseRight => MouseRight1 || MouseRight2; - public bool MouseLeft1 => (ButtonState & ReplayButtonState.Left1) > 0; - public bool MouseRight1 => (ButtonState & ReplayButtonState.Right1) > 0; - public bool MouseLeft2 => (ButtonState & ReplayButtonState.Left2) > 0; - public bool MouseRight2 => (ButtonState & ReplayButtonState.Right2) > 0; + public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1); + public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1); + public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2); + public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2); public ReplayButtonState ButtonState; diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 0bfde148e7..ee34e2df04 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -56,6 +56,12 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } + /// + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. + /// + public virtual double GameplayStartTime => Objects.First().StartTime - 2000; + private readonly Lazy playfield; /// @@ -83,6 +89,14 @@ namespace osu.Game.Rulesets.UI Ruleset = ruleset; playfield = new Lazy(CreatePlayfield); + IsPaused.ValueChanged += paused => + { + if (HasReplayLoaded) + return; + + KeyBindingInputManager.UseParentInput = !paused; + }; + Cursor = CreateCursor(); } @@ -114,6 +128,11 @@ namespace osu.Game.Rulesets.UI public Replay Replay { get; private set; } + /// + /// Whether the game is paused. Used to block user input. + /// + public readonly BindableBool IsPaused = new BindableBool(); + /// /// Sets a replay to be used, overriding local input. /// diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index cfcfca157b..7146ad8064 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -57,22 +57,23 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; + /// + /// The direction in which s in this should scroll. + /// protected readonly Bindable Direction = new Bindable(); /// /// Creates a new . /// - /// The direction in which s in this container should scroll. /// The width to scale the internal coordinate space to. /// May be null if scaling based on is desired. If is also null, no scaling will occur. /// /// The height to scale the internal coordinate space to. /// May be null if scaling based on is desired. If is also null, no scaling will occur. /// - protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null) + protected ScrollingPlayfield(float? customWidth = null, float? customHeight = null) : base(customWidth, customHeight) { - Direction.Value = direction; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs index f46cc7ef9d..5928fbaa1b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs @@ -27,16 +27,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline public TimelineButton() { - InternalChild = button = new IconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - IconColour = OsuColour.Gray(0.35f), - IconHoverColour = Color4.White, - HoverColour = OsuColour.Gray(0.25f), - FlashColour = OsuColour.Gray(0.5f), - Action = () => Action?.Invoke() - }; + InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() }; button.Enabled.BindTo(Enabled); Width = button.ButtonSize.X; @@ -48,5 +39,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight); } + + private class TimelineIconButton : IconButton + { + public TimelineIconButton() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + IconColour = OsuColour.Gray(0.35f); + IconHoverColour = Color4.White; + HoverColour = OsuColour.Gray(0.25f); + FlashColour = OsuColour.Gray(0.5f); + } + } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d922f49ce3..4790996833 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Menu OnChart = delegate { Push(new ChartListing()); }, OnDirect = delegate { Push(new OnlineListing()); }, OnEdit = delegate { Push(new Editor()); }, - OnSolo = delegate { Push(consumeSongSelect()); }, + OnSolo = onSolo, OnMulti = delegate { Push(new Multiplayer()); }, OnExit = Exit, } @@ -85,6 +85,10 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(songSelect = new PlaySongSelect()); } + public void LoadToSolo() => Schedule(onSolo); + + private void onSolo() => Push(consumeSongSelect()); + private Screen consumeSongSelect() { var s = songSelect; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f920b20649..4f3fe15211 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -195,7 +195,6 @@ namespace osu.Game.Screens.Play protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection { - IsCounting = true, FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 2a0587133b..2c31e61114 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play private Container textLayer; private SpriteText countSpriteText; - public bool IsCounting { get; set; } + public bool IsCounting { get; set; } = true; private int countPresses; public int CountPresses { diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 114ea83ba6..721d925d63 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -53,8 +53,7 @@ namespace osu.Game.Screens.Play configVisibility.BindValueChanged(_ => updateVisibility(), true); } - //further: change default values here and in KeyCounter if needed, instead of passing them in every constructor - private bool isCounting; + private bool isCounting = true; public bool IsCounting { get { return isCounting; } diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index e2f133dde3..d9677e5daf 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play /// public class PauseContainer : Container { - public bool IsPaused { get; private set; } + public readonly BindableBool IsPaused = new BindableBool(); public Func CheckCanPause; @@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play public Action OnRetry; public Action OnQuit; - public Action OnResume; - public Action OnPause; - private readonly FramedClock framedClock; private readonly DecoupleableInterpolatingFramedClock decoupledClock; @@ -84,9 +82,8 @@ namespace osu.Game.Screens.Play // stop the seekable clock (stops the audio eventually) decoupledClock.Stop(); - IsPaused = true; + IsPaused.Value = true; - OnPause?.Invoke(); pauseOverlay.Show(); lastPauseActionTime = Time.Current; @@ -96,7 +93,7 @@ namespace osu.Game.Screens.Play { if (!IsPaused) return; - IsPaused = false; + IsPaused.Value = false; IsResuming = false; lastPauseActionTime = Time.Current; @@ -105,7 +102,6 @@ namespace osu.Game.Screens.Play decoupledClock.Seek(decoupledClock.CurrentTime); decoupledClock.Start(); - OnResume?.Invoke(); pauseOverlay.Hide(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d8b714529b..f28ddb09a2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -138,10 +138,9 @@ namespace osu.Game.Screens.Play sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - var firstObjectTime = RulesetContainer.Objects.First().StartTime; adjustableClock.Seek(AllowLeadIn - ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)) - : firstObjectTime); + ? Math.Min(RulesetContainer.GameplayStartTime, beatmap.HitObjects.First().StartTime - beatmap.BeatmapInfo.AudioLeadIn) + : RulesetContainer.GameplayStartTime); adjustableClock.ProcessFrame(); @@ -163,15 +162,10 @@ namespace osu.Game.Screens.Play { pauseContainer = new PauseContainer(offsetClock, adjustableClock) { + Retries = RestartCount, OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - OnPause = () => - { - pauseContainer.Retries = RestartCount; - hudOverlay.KeyCounter.IsCounting = !pauseContainer.IsPaused; - }, - OnResume = () => hudOverlay.KeyCounter.IsCounting = true, Children = new[] { storyboardContainer = new Container @@ -199,7 +193,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new SkipOverlay(firstObjectTime) + new SkipOverlay(RulesetContainer.GameplayStartTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, @@ -219,9 +213,7 @@ namespace osu.Game.Screens.Play { if (!IsCurrentScreen) return; - //we want to hide the hitrenderer immediately (looks better). - //we may be able to remove this once the mouse cursor trail is improved. - RulesetContainer?.Hide(); + pauseContainer.Hide(); Restart(); }, } @@ -230,6 +222,8 @@ namespace osu.Game.Screens.Play hudOverlay.HoldToQuit.Action = Exit; hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); + RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused); + if (ShowStoryboard) initializeStoryboard(false); diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs index 228633a41f..0c4b369f36 100644 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Select.Leaderboards { Rank = newRank; - if (IsLoaded) + if (LoadState >= LoadState.Ready) updateTexture(); } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 8ce40fcfa0..a346911ca2 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -84,10 +84,10 @@ namespace osu.Game.Screens.Select protected override void UpdateBeatmap(WorkingBeatmap beatmap) { - base.UpdateBeatmap(beatmap); - beatmap.Mods.BindTo(SelectedMods); + base.UpdateBeatmap(beatmap); + BeatmapDetails.Beatmap = beatmap; if (beatmap.Track != null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e6926c118b..234508a195 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using OpenTK; using OpenTK.Input; using osu.Framework.Allocation; @@ -20,6 +21,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; @@ -70,7 +72,14 @@ namespace osu.Game.Screens.Select private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); + dependencies.CacheAs(Ruleset); + dependencies.CacheAs>(Ruleset); + + return dependencies; + } protected SongSelect() { @@ -189,9 +198,9 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) { - dependencies.CacheAs(this); - dependencies.CacheAs(Ruleset); - dependencies.CacheAs>(Ruleset); + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce); + Ruleset.ValueChanged += r => base.Ruleset.Value = r; if (Footer != null) { @@ -277,7 +286,7 @@ namespace osu.Game.Screens.Select // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) { - Ruleset.Value = beatmap.BeatmapInfo.Ruleset; + base.Ruleset.Value = beatmap.BeatmapInfo.Ruleset; Carousel.SelectBeatmap(beatmap.BeatmapInfo); } } @@ -295,18 +304,25 @@ namespace osu.Game.Screens.Select void performLoad() { + WorkingBeatmap working = Beatmap.Value; + bool preview = false; + // We may be arriving here due to another component changing the bindable Beatmap. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (beatmap?.Equals(Beatmap.Value.BeatmapInfo) != true) { - bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); - ensurePlayingSelected(preview); + preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; + working = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); } + + working.Mods.Value = Enumerable.Empty(); + + Beatmap.Value = working; Ruleset.Value = ruleset; + ensurePlayingSelected(preview); + UpdateBeatmap(Beatmap.Value); } diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 0ef54c7310..d4f1c5c6f1 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -14,6 +14,8 @@ namespace osu.Game.Skinning protected override void ParseLine(SkinConfiguration skin, Section section, string line) { + line = StripComments(line); + switch (section) { case Section.General: diff --git a/osu.Game/Tests/Platform/TestStorage.cs b/osu.Game/Tests/Platform/TestStorage.cs deleted file mode 100644 index 5b31c7b4d0..0000000000 --- a/osu.Game/Tests/Platform/TestStorage.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Platform; - -namespace osu.Game.Tests.Platform -{ - public class TestStorage : DesktopStorage - { - public TestStorage(string baseName) : base(baseName, null) - { - } - - public override string GetDatabaseConnectionString(string name) - { - return "DataSource=:memory:"; - } - } -} diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index fcbab5b8f5..8e09ec849c 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Configuration; +using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -20,6 +22,9 @@ namespace osu.Game.Tests.Visual protected DependencyContainer Dependencies { get; private set; } + private readonly Lazy localStorage; + protected Storage LocalStorage => localStorage.Value; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -33,6 +38,11 @@ namespace osu.Game.Tests.Visual return Dependencies; } + protected OsuTestCase() + { + localStorage = new Lazy(() => new DesktopStorage($"{GetType().Name}-{Guid.NewGuid()}", null)); + } + [BackgroundDependencyLoader] private void load(AudioManager audioManager, RulesetStore rulesets) { @@ -50,6 +60,9 @@ namespace osu.Game.Tests.Visual beatmap.Disabled = true; beatmap.Value.Track.Stop(); } + + if (localStorage.IsValueCreated) + localStorage.Value.DeleteDirectory("."); } protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 98b3a2df0c..80039eadad 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -43,7 +43,7 @@ namespace osu.Game.Users country = value; - if (IsLoaded) + if (LoadState >= LoadState.Ready) sprite.Texture = getFlagTexture(); } }