diff --git a/osu-framework b/osu-framework index d00a7df902..42e26d49b9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f +Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162 diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 933fe0787c..8446b7e70f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Database; +using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; namespace osu.Game.Rulesets.Mania.Beatmaps { @@ -83,28 +84,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps // Following lines currently commented out to appease resharper - //Patterns.PatternGenerator conversion = null; + Patterns.PatternGenerator conversion = null; if (distanceData != null) - { - // Slider - } + conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern); else if (endTimeData != null) - { - // Spinner - } + conversion = new EndTimeObjectPatternGenerator(random, original, beatmap); else if (positionData != null) { // Circle } - //if (conversion == null) - return null; + if (conversion == null) + return null; - //Pattern newPattern = conversion.Generate(); - //lastPattern = newPattern; + Pattern newPattern = conversion.Generate(); + lastPattern = newPattern; - //return newPattern.HitObjects; + return newPattern.HitObjects; } /// diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs new file mode 100644 index 0000000000..0cad23304e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -0,0 +1,490 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + /// + /// A pattern generator for IHasDistance hit objects. + /// + internal class DistanceObjectPatternGenerator : PatternGenerator + { + /// + /// Base osu! slider scoring distance. + /// + private const float osu_base_scoring_distance = 100; + + private readonly double endTime; + private readonly double segmentDuration; + private readonly int repeatCount; + + private PatternType convertType; + + public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) + : base(random, hitObject, beatmap, previousPattern) + { + ControlPoint overridePoint; + ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint); + + convertType = PatternType.None; + if ((overridePoint ?? controlPoint)?.KiaiMode == false) + convertType = PatternType.LowProbability; + + var distanceData = hitObject as IHasDistance; + var repeatsData = hitObject as IHasRepeats; + + repeatCount = repeatsData?.RepeatCount ?? 1; + + double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime); + double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment; + + // The true distance, accounting for any repeats + double distance = (distanceData?.Distance ?? 0) * repeatCount; + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + endTime = hitObject.StartTime + osuDuration; + segmentDuration = (endTime - HitObject.StartTime) / repeatCount; + } + + public override Pattern Generate() + { + if (repeatCount > 1) + { + if (segmentDuration <= 90) + return generateRandomHoldNotes(HitObject.StartTime, 1); + + if (segmentDuration <= 120) + { + convertType |= PatternType.ForceNotStack; + return generateRandomNotes(HitObject.StartTime, repeatCount + 1); + } + + if (segmentDuration <= 160) + return generateStair(HitObject.StartTime); + + if (segmentDuration <= 200 && ConversionDifficulty > 3) + return generateRandomMultipleNotes(HitObject.StartTime); + + double duration = endTime - HitObject.StartTime; + if (duration >= 4000) + return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); + + if (segmentDuration > 400 && repeatCount < AvailableColumns - 1 - RandomStart) + return generateTiledHoldNotes(HitObject.StartTime); + + return generateHoldAndNormalNotes(HitObject.StartTime); + } + + if (segmentDuration <= 110) + { + if (PreviousPattern.ColumnWithObjects < AvailableColumns) + convertType |= PatternType.ForceNotStack; + else + convertType &= ~PatternType.ForceNotStack; + return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); + } + + if (ConversionDifficulty > 6.5) + { + if ((convertType & PatternType.LowProbability) > 0) + 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) + 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) + return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); + } + + if ((convertType & PatternType.LowProbability) > 0) + return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); + } + + /// + /// Generates random hold notes that start at an span the same amount of rows. + /// + /// Start time of each hold note. + /// Number of hold notes. + /// The containing the hit objects. + private Pattern generateRandomHoldNotes(double startTime, int noteCount) + { + // - - - - + // ■ - ■ ■ + // □ - □ □ + // ■ - ■ ■ + + var pattern = new Pattern(); + + int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects; + int nextColumn = Random.Next(RandomStart, AvailableColumns); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) + { + while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column + nextColumn = Random.Next(RandomStart, AvailableColumns); + addToPattern(pattern, nextColumn, startTime, endTime); + } + + // This is can't be combined with the above loop due to RNG + for (int i = 0; i < noteCount - usableColumns; i++) + { + while (pattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, AvailableColumns); + addToPattern(pattern, nextColumn, startTime, endTime); + } + + return pattern; + } + + /// + /// Generates random notes, with one note per row and no stacking. + /// + /// The start time. + /// The number of notes. + /// The containing the hit objects. + private Pattern generateRandomNotes(double startTime, int noteCount) + { + // - - - - + // x - - - + // - - x - + // - - - x + // x - - - + + var pattern = new Pattern(); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns) + { + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, AvailableColumns); + } + + int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) + { + addToPattern(pattern, nextColumn, startTime, startTime); + while (nextColumn == lastColumn) + nextColumn = Random.Next(RandomStart, AvailableColumns); + + lastColumn = nextColumn; + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates a stair of notes, with one note per row. + /// + /// The start time. + /// The containing the hit objects. + private Pattern generateStair(double startTime) + { + // - - - - + // x - - - + // - x - - + // - - x - + // - - - x + // - - x - + // - x - - + // x - - - + + var pattern = new Pattern(); + + int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + bool increasing = Random.NextDouble() > 0.5; + + for (int i = 0; i <= repeatCount; i++) + { + addToPattern(pattern, column, startTime, startTime); + startTime += segmentDuration; + + // Check if we're at the borders of the stage, and invert the pattern if so + if (increasing) + { + if (column >= AvailableColumns - 1) + { + increasing = false; + column--; + } + else + column++; + } + else + { + if (column <= RandomStart) + { + increasing = true; + column++; + } + else + column--; + } + } + + return pattern; + } + + /// + /// Generates random notes with 1-2 notes per row and no stacking. + /// + /// The start time. + /// The containing the hit objects. + private Pattern generateRandomMultipleNotes(double startTime) + { + // - - - - + // x - - - + // - x x - + // - - - x + // x - x - + + var pattern = new Pattern(); + + bool legacy = AvailableColumns >= 4 && AvailableColumns <= 8; + int interval = Random.Next(1, AvailableColumns - (legacy ? 1 : 0)); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= repeatCount; i++) + { + addToPattern(pattern, nextColumn, startTime, startTime); + + nextColumn += interval; + if (nextColumn >= AvailableColumns - RandomStart) + nextColumn = nextColumn - AvailableColumns - RandomStart + (legacy ? 1 : 0); + nextColumn += RandomStart; + + // If we're in 2K, let's not add many consecutive doubles + if (AvailableColumns > 2) + addToPattern(pattern, nextColumn, startTime, startTime); + + nextColumn = Random.Next(RandomStart, AvailableColumns); + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates random hold notes. The amount of hold notes generated is determined by probabilities. + /// + /// The hold note start time. + /// The probability required for 2 hold notes to be generated. + /// The probability required for 3 hold notes to be generated. + /// The probability required for 4 hold notes to be generated. + /// The containing the hit objects. + private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) + { + // - - - - + // ■ - ■ ■ + // □ - □ □ + // ■ - ■ ■ + + switch (AvailableColumns) + { + case 2: + p2 = 0; + p3 = 0; + p4 = 0; + break; + case 3: + p2 = Math.Max(p2, 0.1); + p3 = 0; + p4 = 0; + break; + case 4: + p2 = Math.Max(p2, 0.3); + p3 = Math.Max(p3, 0.04); + p4 = 0; + break; + case 5: + p2 = Math.Max(p2, 0.34); + p3 = Math.Max(p3, 0.1); + p4 = Math.Max(p4, 0.03); + break; + } + + Func isDoubleSample = sample => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH; + + bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; + canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); + + if (canGenerateTwoNotes) + p2 = 1; + + return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4)); + } + + /// + /// Generates tiled hold notes. You can think of this as a stair of hold notes. + /// + /// The first hold note start time. + /// The containing the hit objects. + private Pattern generateTiledHoldNotes(double startTime) + { + // - - - - + // ■ ■ ■ ■ + // □ □ □ □ + // □ □ □ □ + // □ □ □ ■ + // □ □ ■ - + // □ ■ - - + // ■ - - - + + var pattern = new Pattern(); + + int columnRepeat = Math.Min(repeatCount, AvailableColumns); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns) + { + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, AvailableColumns); + } + + for (int i = 0; i < columnRepeat; i++) + { + while (pattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, AvailableColumns); + + addToPattern(pattern, nextColumn, startTime, endTime); + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates a hold note alongside normal notes. + /// + /// The start time of notes. + /// The containing the hit objects. + private Pattern generateHoldAndNormalNotes(double startTime) + { + // - - - - + // ■ x x - + // ■ - x x + // ■ x - x + // ■ - x x + + var pattern = new Pattern(); + + int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns) + { + while (PreviousPattern.ColumnHasObject(holdColumn)) + holdColumn = Random.Next(RandomStart, AvailableColumns); + } + + // Create the hold note + addToPattern(pattern, holdColumn, startTime, endTime); + + int noteCount = 1; + if (ConversionDifficulty > 6.5) + noteCount = GetRandomNoteCount(0.63, 0); + else if (ConversionDifficulty > 4) + noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0.12 : 0.45, 0); + else if (ConversionDifficulty > 2.5) + noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0 : 0.24, 0); + noteCount = Math.Min(AvailableColumns - 1, noteCount); + + bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); + int nextColumn = Random.Next(RandomStart, AvailableColumns); + + var rowPattern = new Pattern(); + for (int i = 0; i <= repeatCount; i++) + { + if (!(ignoreHead && startTime == HitObject.StartTime)) + { + for (int j = 0; j < noteCount; j++) + { + while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn) + nextColumn = Random.Next(RandomStart, AvailableColumns); + addToPattern(rowPattern, nextColumn, startTime, startTime); + } + } + + pattern.Add(rowPattern); + rowPattern.Clear(); + + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Retrieves the sample info list at a point in time. + /// + /// The time to retrieve the sample info list from. + /// + private SampleInfoList sampleInfoListAt(double time) + { + var curveData = HitObject as IHasCurve; + + if (curveData == null) + return HitObject.Samples; + + double segmentTime = (endTime - HitObject.StartTime) / repeatCount; + + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.RepeatSamples[index]; + } + + + /// + /// Constructs and adds a note to a pattern. + /// + /// The pattern to add to. + /// The column to add the note to. + /// The start time of the note. + /// The end time of the note (set to for a non-hold note). + private void addToPattern(Pattern pattern, int column, double startTime, double endTime) + { + ManiaHitObject newObject; + + if (startTime == endTime) + { + newObject = new Note + { + StartTime = startTime, + Samples = sampleInfoListAt(startTime), + Column = column + }; + } + else + { + newObject = new HoldNote + { + StartTime = startTime, + Samples = sampleInfoListAt(startTime), + EndSamples = sampleInfoListAt(endTime), + Column = column, + Duration = endTime - startTime + }; + } + + pattern.Add(newObject); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs new file mode 100644 index 0000000000..8f438f9ff4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -0,0 +1,98 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + internal class EndTimeObjectPatternGenerator : PatternGenerator + { + private readonly double endTime; + + public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap) + : base(random, hitObject, beatmap, new Pattern()) + { + var endtimeData = HitObject as IHasEndTime; + + endTime = endtimeData?.EndTime ?? 0; + } + + public override Pattern Generate() + { + var pattern = new Pattern(); + + bool generateHold = endTime - HitObject.StartTime >= 100; + + if (AvailableColumns == 8) + { + if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) + addToPattern(pattern, 0, generateHold); + else + addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); + } + else if (AvailableColumns > 0) + addToPattern(pattern, getNextRandomColumn(0), generateHold); + + return pattern; + } + + /// + /// Picks a random column after a column. + /// + /// The starting column. + /// A random column after . + private int getNextRandomColumn(int start) + { + int nextColumn = Random.Next(start, AvailableColumns); + + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(start, AvailableColumns); + + return nextColumn; + } + + /// + /// Constructs and adds a note to a pattern. + /// + /// The pattern to add to. + /// The column to add the note to. + /// Whether to add a hold note. + private void addToPattern(Pattern pattern, int column, bool holdNote) + { + ManiaHitObject newObject; + + if (holdNote) + { + newObject = new HoldNote + { + StartTime = HitObject.StartTime, + EndSamples = HitObject.Samples, + Column = column, + Duration = endTime - HitObject.StartTime + }; + + newObject.Samples.Add(new SampleInfo + { + Name = SampleInfo.HIT_NORMAL + }); + } + else + { + newObject = new Note + { + StartTime = HitObject.StartTime, + Samples = HitObject.Samples, + Column = column + }; + } + + pattern.Add(newObject); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index ad07c03b96..e6e3f1d07f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy HitObject firstObject = Beatmap.HitObjects.FirstOrDefault(); double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); - drainTime -= Beatmap.EventInfo.TotalBreakTime; + drainTime -= Beatmap.TotalBreakTime; if (drainTime == 0) drainTime = 10000; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs index d4957d41a9..d645882511 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs @@ -15,51 +15,51 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// Keep the same as last row. /// - ForceStack = 1, + ForceStack = 1 << 0, /// /// Keep different from last row. /// - ForceNotStack = 2, + ForceNotStack = 1 << 1, /// /// Keep as single note at its original position. /// - KeepSingle = 4, + KeepSingle = 1 << 2, /// /// Use a lower random value. /// - LowProbability = 8, + LowProbability = 1 << 3, /// /// Reserved. /// - Alternate = 16, + Alternate = 1 << 4, /// /// Ignore the repeat count. /// - ForceSigSlider = 32, + ForceSigSlider = 1 << 5, /// /// Convert slider to circle. /// - ForceNotSlider = 64, + ForceNotSlider = 1 << 6, /// /// Notes gathered together. /// - Gathered = 128, - Mirror = 256, + Gathered = 1 << 7, + Mirror = 1 << 8, /// /// Change 0 -> 6. /// - Reverse = 512, + Reverse = 1 << 9, /// /// 1 -> 5 -> 1 -> 5 like reverse. /// - Cycle = 1024, + Cycle = 1 << 10, /// /// Next note will be at column + 1. /// - Stair = 2048, + Stair = 1 << 11, /// /// Next note will be at column - 1. /// - ReverseStair = 4096 + ReverseStair = 1 << 12 } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index cbde1f0f53..15d31406e9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns @@ -21,16 +20,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns public IEnumerable HitObjects => hitObjects; /// - /// Whether this pattern already contains a hit object in a code. + /// Check whether a column of this patterns contains a hit object. /// /// The column index. - /// Whether this pattern already contains a hit object in - public bool IsFilled(int column) => hitObjects.Exists(h => h.Column == column); + /// Whether the column with index contains a hit object. + public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column); /// /// Amount of columns taken up by hit objects in this pattern. /// - public int ColumnsFilled => HitObjects.GroupBy(h => h.Column).Count(); + public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count(); /// /// Adds a hit object to this pattern. @@ -42,10 +41,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// Copies hit object from another pattern to this one. /// /// The other pattern. - public void Add(Pattern other) - { - other.HitObjects.ForEach(Add); - } + public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects); /// /// Clears this pattern, removing all hit objects. diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index a25b8fbf2a..701947c381 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Audio; using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Rulesets.Mania.Judgements; @@ -22,6 +23,11 @@ namespace osu.Game.Rulesets.Mania.Objects public double Duration { get; set; } public double EndTime => StartTime + Duration; + /// + /// The samples to be played when this hold note is released. + /// + public SampleInfoList EndSamples = new SampleInfoList(); + /// /// The key-release hit windows for this hold note. /// diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index f6eb4aea2c..93aaa94f45 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -9,11 +9,5 @@ namespace osu.Game.Rulesets.Mania.Objects public abstract class ManiaHitObject : HitObject, IHasColumn { public int Column { get; set; } - - /// - /// The number of other that start at - /// the same time as this hit object. - /// - public int Siblings { get; set; } } } diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs index 6c39ba40f9..2ff97047c0 100644 --- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs +++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Timing var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault(); if (controlPoint == null) - throw new Exception("Could not find suitable timing section to add object to."); + throw new InvalidOperationException("Could not find suitable timing section to add object to."); controlPoint.Add(drawable); } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 0bf70017e3..c67866dc10 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange); if (firstTimingChange == null) - throw new Exception("The Beatmap contains no timing points!"); + throw new InvalidOperationException("The Beatmap contains no timing points!"); // Generate the timing points, making non-timing changes use the previous timing change var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c => diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index ec426c895f..adcdfd5fae 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -47,6 +47,8 @@ + + diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 854a9b5f49..3722d13ffc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly TextAwesome symbol; + private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c"); + private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c"); + private Color4 normalColour; private Color4 completeColour; @@ -154,13 +157,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuColour colours) { - normalColour = colours.SpinnerBase; + normalColour = baseColour; background.AccentColour = normalColour; - completeColour = colours.YellowLight.Opacity(0.6f); + completeColour = colours.YellowLight.Opacity(0.75f); - disc.AccentColour = colours.SpinnerFill; + disc.AccentColour = fillColour; circle.Colour = colours.BlueDark; glow.Colour = colours.BlueDark; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 1c54f9f893..66cf7758b9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -28,9 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces EdgeEffect = new EdgeEffect { + Hollow = true, Type = EdgeEffectType.Glow, - Radius = 14, - Colour = value.Opacity(0.3f), + Radius = 40, + Colour = value, }; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 4e4d4e30b9..29d6d1f147 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (Complete && updateCompleteTick()) { background.Flush(flushType: typeof(TransformAlpha)); - background.FadeTo(tracking_alpha + 0.4f, 60, EasingTypes.OutExpo); + background.FadeTo(tracking_alpha + 0.2f, 60, EasingTypes.OutExpo); background.Delay(60); background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index dc3d18d40a..4dbb6bd4d6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -2,12 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; @@ -15,24 +13,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerTicks : Container { - private Color4 glowColour; - public SpinnerTicks() { Origin = Anchor.Centre; Anchor = Anchor.Centre; RelativeSizeAxes = Axes.Both; - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - glowColour = colours.BlueDarker.Opacity(0.4f); - layout(); - } - - private void layout() - { const int count = 18; for (int i = 0; i < count; i++) @@ -44,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces EdgeEffect = new EdgeEffect { Type = EdgeEffectType.Glow, - Radius = 20, - Colour = glowColour, + Radius = 10, + Colour = Color4.Gray.Opacity(0.2f), }, RelativePositionAxes = Axes.Both, Masking = true, diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index a64002e0b0..608b2fcd19 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -2,11 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK.Graphics; -using osu.Game.Beatmaps.Events; using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Rulesets.Objects; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Beatmaps { @@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps { public BeatmapInfo BeatmapInfo; public TimingInfo TimingInfo = new TimingInfo(); - public EventInfo EventInfo = new EventInfo(); + public List Breaks = new List(); public readonly List ComboColors = new List { new Color4(17, 136, 170, 255), @@ -34,6 +34,11 @@ namespace osu.Game.Beatmaps /// public List HitObjects; + /// + /// Total amount of break time in the beatmap. + /// + public double TotalBreakTime => Breaks.Sum(b => b.Duration); + /// /// Constructs a new beatmap. /// @@ -42,7 +47,7 @@ namespace osu.Game.Beatmaps { BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo; TimingInfo = original?.TimingInfo ?? TimingInfo; - EventInfo = original?.EventInfo ?? EventInfo; + Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; } } diff --git a/osu.Game/Beatmaps/Events/BackgroundEvent.cs b/osu.Game/Beatmaps/Events/BackgroundEvent.cs deleted file mode 100644 index 215373bd3b..0000000000 --- a/osu.Game/Beatmaps/Events/BackgroundEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Events -{ - public class BackgroundEvent : Event - { - /// - /// The file name. - /// - public string Filename; - } -} diff --git a/osu.Game/Beatmaps/Events/Event.cs b/osu.Game/Beatmaps/Events/Event.cs deleted file mode 100644 index 3af3909462..0000000000 --- a/osu.Game/Beatmaps/Events/Event.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Events -{ - public abstract class Event - { - /// - /// The event start time. - /// - public double StartTime; - } -} diff --git a/osu.Game/Beatmaps/Events/EventInfo.cs b/osu.Game/Beatmaps/Events/EventInfo.cs deleted file mode 100644 index 3ba3d5ba03..0000000000 --- a/osu.Game/Beatmaps/Events/EventInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Beatmaps.Events -{ - public class EventInfo - { - /// - /// All the background events. - /// - public readonly List Backgrounds = new List(); - - /// - /// All the break events. - /// - public readonly List Breaks = new List(); - - /// - /// Total duration of all breaks. - /// - public double TotalBreakTime => Breaks.Sum(b => b.Duration); - - /// - /// Retrieves the active background at a time. - /// - /// The time to retrieve the background at. - /// The background. - public BackgroundEvent BackgroundAt(double time) => Backgrounds.FirstOrDefault(b => b.StartTime <= time); - } -} diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 772c0e9c07..04208337c7 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using System.IO; using OpenTK.Graphics; -using osu.Game.Beatmaps.Events; using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects.Legacy; @@ -217,18 +216,12 @@ namespace osu.Game.Beatmaps.Formats case EventType.Background: string filename = split[2].Trim('"'); - beatmap.EventInfo.Backgrounds.Add(new BackgroundEvent - { - StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), - Filename = filename - }); - if (type == EventType.Background) beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; break; case EventType.Break: - var breakEvent = new BreakEvent + var breakEvent = new BreakPeriod { StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) @@ -237,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats if (!breakEvent.HasEffect) return; - beatmap.EventInfo.Breaks.Add(breakEvent); + beatmap.Breaks.Add(breakEvent); break; } } diff --git a/osu.Game/Beatmaps/Events/BreakEvent.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs similarity index 70% rename from osu.Game/Beatmaps/Events/BreakEvent.cs rename to osu.Game/Beatmaps/Timing/BreakPeriod.cs index 78e33f2fbb..fb307b7144 100644 --- a/osu.Game/Beatmaps/Events/BreakEvent.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,27 +1,32 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Beatmaps.Events +namespace osu.Game.Beatmaps.Timing { - public class BreakEvent : Event + public class BreakPeriod { /// /// The minimum duration required for a break to have any effect. /// private const double min_break_duration = 650; + /// + /// The break start time. + /// + public double StartTime; + /// /// The break end time. /// public double EndTime; /// - /// The duration of the break. + /// The break duration. /// public double Duration => EndTime - StartTime; /// - /// Whether the break has any effect. Breaks that are too short are culled before they reach the EventInfo. + /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// public bool HasEffect => Duration >= min_break_duration; } diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index d7f5d6c112..945de822a3 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -11,38 +11,42 @@ namespace osu.Game.Graphics.Containers { public class BeatSyncedContainer : Container { - private Bindable beatmap; - private int lastBeat; - private double lastTimingPointStart; - //This is to avoid sending new beats when not at the very start of the beat + /// + /// A new beat will not be sent if the time since the beat is larger than this tolerance. + /// private const int seek_tolerance = 20; - private const double min_beat_length = 1E-100; + + private readonly Bindable beatmap = new Bindable(); + + private int lastBeat; + private ControlPoint lastControlPoint; protected override void Update() { - if (beatmap.Value != null) - { - double trackCurrentTime = beatmap.Value.Track.CurrentTime; - ControlPoint kiaiControlPoint; - ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(trackCurrentTime, out kiaiControlPoint); + if (beatmap.Value?.Track == null) + return; - if (controlPoint != null) - { - double beatLength = controlPoint.BeatLength; - bool kiai = kiaiControlPoint?.KiaiMode ?? false; - double timingPointStart = controlPoint.Time; - int beat = beatLength > min_beat_length ? (int)((trackCurrentTime - timingPointStart) / beatLength) : 0; + double currentTrackTime = beatmap.Value.Track.CurrentTime; + ControlPoint overridePoint; + ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint); - //The beats before the start of the first control point are off by 1, this should do the trick - if (trackCurrentTime < timingPointStart) - beat--; + bool kiai = (overridePoint ?? controlPoint).KiaiMode; + int beat = controlPoint.BeatLength > 0 ? (int)((currentTrackTime - controlPoint.Time) / controlPoint.BeatLength) : 0; - if ((timingPointStart != lastTimingPointStart || beat != lastBeat) && (int)((trackCurrentTime - timingPointStart) % beatLength) <= seek_tolerance) - OnNewBeat(beat, beatLength, controlPoint.TimeSignature, kiai); - lastBeat = beat; - lastTimingPointStart = timingPointStart; - } - } + // The beats before the start of the first control point are off by 1, this should do the trick + if (currentTrackTime < controlPoint.Time) + beat--; + + if (controlPoint == lastControlPoint && beat == lastBeat) + return; + + if ((currentTrackTime - controlPoint.Time) % controlPoint.BeatLength > seek_tolerance) + return; + + OnNewBeat(beat, controlPoint.BeatLength, controlPoint.TimeSignature, kiai); + + lastBeat = beat; + lastControlPoint = controlPoint; } protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai) @@ -52,7 +56,7 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuGameBase game) { - beatmap = game.Beatmap; + beatmap.BindTo(game.Beatmap); } } } diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 697f8f4629..3d83668d07 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -87,8 +87,5 @@ namespace osu.Game.Graphics public readonly Color4 RedDarker = FromHex(@"870000"); public readonly Color4 ChatBlue = FromHex(@"17292e"); - - public readonly Color4 SpinnerBase = FromHex(@"002c3c"); - public readonly Color4 SpinnerFill = FromHex(@"005b7c"); } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 2f344e0bdf..7e3bb44465 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -288,7 +288,7 @@ namespace osu.Game.Online.API { APIRequest req; while (oldQueue.TryDequeue(out req)) - req.Fail(new Exception(@"Disconnected from server")); + req.Fail(new WebException(@"Disconnected from server")); } } diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index cf52f9ccd3..858015e29b 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.IO.Network; using osu.Game.Online.Chat; @@ -20,10 +21,7 @@ namespace osu.Game.Online.API.Requests protected override WebRequest CreateWebRequest() { - string channelString = string.Empty; - foreach (Channel c in channels) - channelString += c.Id + ","; - channelString = channelString.TrimEnd(','); + string channelString = string.Join(",", channels.Select(x => x.Id)); var req = base.CreateWebRequest(); req.AddParameter(@"channels", channelString); diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 2925c3ccb4..93fd0a8956 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online.Chat [JsonProperty(@"channel_id")] public int Id; - public readonly SortedList Messages = new SortedList((m1, m2) => m1.Id.CompareTo(m2.Id)); + public readonly SortedList Messages = new SortedList(Comparer.Default); //internal bool Joined; diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index bf53a68910..4c7e099647 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -8,7 +8,7 @@ using osu.Game.Users; namespace osu.Game.Online.Chat { - public class Message + public class Message : IComparable, IEquatable { [JsonProperty(@"message_id")] public readonly long Id; @@ -42,17 +42,11 @@ namespace osu.Game.Online.Chat Id = id; } - public override bool Equals(object obj) - { - var objMessage = obj as Message; + public int CompareTo(Message other) => Id.CompareTo(other.Id); - return Id == objMessage?.Id; - } + public bool Equals(Message other) => Id == other?.Id; - public override int GetHashCode() - { - return Id.GetHashCode(); - } + public override int GetHashCode() => Id.GetHashCode(); } public enum TargetType diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 355aeed134..50c849f00e 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Chat public class DrawableChannel : Container { public readonly Channel Channel; - private readonly FillFlowContainer flow; + private readonly FillFlowContainer flow; private readonly ScrollContainer scroll; public DrawableChannel(Channel channel) @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Chat RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - flow = new FillFlowContainer + flow = new FillFlowContainer { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -63,19 +63,18 @@ namespace osu.Game.Overlays.Chat var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + //up to last Channel.MAX_HISTORY messages + flow.Add(displayMessages.Select(m => new ChatLine(m))); + if (scroll.IsScrolledToEnd(10) || !flow.Children.Any()) scrollToEnd(); - //up to last Channel.MAX_HISTORY messages - foreach (Message m in displayMessages) - { - var d = new ChatLine(m); - flow.Add(d); - } + var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + int count = staleMessages.Length - Channel.MAX_HISTORY; - while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY) + for (int i = 0; i < count; i++) { - var d = flow.Children.First(c => c.LifetimeEnd == double.MaxValue); + var d = staleMessages[i]; if (!scroll.IsScrolledToEnd(10)) scroll.OffsetScrollPosition(-d.DrawHeight); d.Expire(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 2836be22ae..686a1d513a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -229,6 +229,7 @@ namespace osu.Game.Overlays Scheduler.Add(delegate { loading.FadeOut(100); + loading.Expire(); addChannel(channels.Find(c => c.Name == @"#lazer")); addChannel(channels.Find(c => c.Name == @"#osu")); @@ -320,11 +321,8 @@ namespace osu.Game.Overlays fetchReq = new GetMessagesRequest(careChannels, lastMessageId); fetchReq.Success += delegate (List messages) { - var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct(); - - //batch messages per channel. - foreach (var id in ids) - careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id).ToArray()); + foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId)) + careChannels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 16bdd6132f..6a0e37ca6f 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -57,12 +57,17 @@ namespace osu.Game.Screens beatmap.Value = localMap; } - beatmap.ValueChanged += OnBeatmapChanged; - if (osuGame != null) ruleset.BindTo(osuGame.Ruleset); } + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.ValueChanged += OnBeatmapChanged; + } + /// /// The global Beatmap was changed. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f9307595d2..3efc85d743 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Play FramedClock = offsetClock, OnRetry = Restart, OnQuit = Exit, - CheckCanPause = () => ValidForResume && !HasFailed, + CheckCanPause = () => ValidForResume && !HasFailed && !HitRenderer.HasReplayLoaded, Retries = RestartCount, OnPause = () => { hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4df24c1314..2d6d212130 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -155,7 +155,11 @@ namespace osu.Game.Screens.Select index = (index + direction + groups.Count) % groups.Count; if (groups[index].State != BeatmapGroupState.Hidden) { - SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap); + if (skipDifficulties) + SelectBeatmap(groups[index].SelectedPanel != null ? groups[index].SelectedPanel.Beatmap : groups[index].BeatmapPanels.First().Beatmap); + else + SelectBeatmap(direction == 1 ? groups[index].BeatmapPanels.First().Beatmap : groups[index].BeatmapPanels.Last().Beatmap); + return; } } while (index != startIndex); @@ -167,10 +171,8 @@ namespace osu.Game.Screens.Select if (visibleGroups.Count < 1) return; BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)]; - BeatmapPanel panel = group?.BeatmapPanels.First(); - if (panel == null) - return; + BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)]; selectGroup(group, panel); } @@ -409,7 +411,14 @@ namespace osu.Game.Screens.Select int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(Current + drawHeight); - if (lastIndex < 0) lastIndex = ~lastIndex; + if (lastIndex < 0) + { + lastIndex = ~lastIndex; + + // Add the first panel of the last visible beatmap group to preload its data. + if (lastIndex != 0 && panels[lastIndex - 1] is BeatmapSetHeader) + lastIndex++; + } // Add those panels within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 6863f209e6..32e09a5f28 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; @@ -68,8 +69,6 @@ namespace osu.Game.Screens.Select public Footer() { - AlwaysReceiveInput = true; - RelativeSizeAxes = Axes.X; Height = HEIGHT; Anchor = Anchor.BottomCentre; @@ -124,5 +123,13 @@ namespace osu.Game.Screens.Select updateModeLight(); } + + protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || StartButton.Contains(screenSpacePos); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnClick(InputState state) => true; + + protected override bool OnDragStart(InputState state) => true; } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 8f91f1ed0f..f96fbb87cb 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -57,14 +57,23 @@ namespace osu.Game.Screens.Select { beatmap?.Mods.BindTo(modSelect.SelectedMods); + if (Beatmap?.Track != null) + Beatmap.Track.Looping = false; + beatmapDetails.Beatmap = beatmap; + if (beatmap?.Track != null) + beatmap.Track.Looping = true; + base.OnBeatmapChanged(beatmap); } protected override void OnResuming(Screen last) { player = null; + + Beatmap.Track.Looping = true; + base.OnResuming(last); } @@ -83,13 +92,21 @@ namespace osu.Game.Screens.Select return true; } - return base.OnExiting(next); + if (base.OnExiting(next)) + return true; + + if (Beatmap?.Track != null) + Beatmap.Track.Looping = false; + + return false; } protected override void OnSelected() { if (player != null) return; + Beatmap.Track.Looping = false; + LoadComponentAsync(player = new PlayerLoader(new Player { Beatmap = Beatmap, //eagerly set this so it's present before push. diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c352e6e034..e9ead7c9c0 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -229,6 +229,8 @@ namespace osu.Game.Screens.Select changeBackground(Beatmap); + selectionChangeNoBounce = Beatmap?.BeatmapInfo; + Content.FadeInFromZero(250); beatmapInfoWedge.State = Visibility.Visible; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 49f453a495..b94c19e1f8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -74,11 +74,6 @@ - - - - - @@ -92,6 +87,7 @@ + @@ -187,7 +183,6 @@ - @@ -297,6 +292,7 @@ + @@ -432,11 +428,11 @@ - + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} osu.Framework - + {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} osu.Game.Resources