diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index ff930b07a3..2bff304fba 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,9 +1,11 @@ -osu!lazer is currently in early stages of development and is not yet ready for end users. Please avoid creating issues or bugs if you do not personally intend to fix them. Some acceptable topics include: +osu!lazer is currently still under heavy development! +Please ensure that you are making an issue for one of the following: + +- A bug with currently implemented features (not features that don't exist) +- A feature you are considering adding, so we can collaborate on feedback and design. - Discussions about technical design decisions -- Bugs that you have found and are personally willing and able to fix -- TODO lists of smaller tasks around larger features - -Basically, issues are not a place for you to get help. They are a place for developers to collaborate on the game. If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide. + +Screenshots and log files are highly welcomed. \ No newline at end of file diff --git a/osu-framework b/osu-framework index 6915954abd..d29c8365ba 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 6915954abdba64e72f698aa58698b00159f3678d +Subproject commit d29c8365ba3cf7924b57cf22341f4af55658764c diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs index ecae154075..ec9dd15673 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs @@ -28,16 +28,14 @@ namespace osu.Game.Rulesets.Catch.Tests { } - protected override Beatmap CreateBeatmap() + protected override Beatmap CreateBeatmap(Ruleset ruleset) { var beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 6, - } + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset.RulesetInfo } }; diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs index 518845208c..8e5843f40a 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs @@ -15,19 +15,18 @@ namespace osu.Game.Rulesets.Catch.Tests { } - protected override Beatmap CreateBeatmap() + protected override Beatmap CreateBeatmap(Ruleset ruleset) { var beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 6, - } + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset.RulesetInfo } }; + for (int i = 0; i < 512; i++) beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs index c01791a923..7564adea8c 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs @@ -15,9 +15,10 @@ namespace osu.Game.Rulesets.Catch.Tests { } - protected override Beatmap CreateBeatmap() + protected override Beatmap CreateBeatmap(Ruleset ruleset) { - var beatmap = new Beatmap(); + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; + for (int i = 0; i < 512; i++) if (i % 5 < 3) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 9922d4c8ad..4734e40803 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps yield break; } - var objects = IsForCurrentRuleset ? generateSpecific(original) : generateConverted(original); + var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); if (objects == null) yield break; @@ -110,10 +110,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// Method that generates hit objects for osu!mania specific beatmaps. /// /// The original hit object. + /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. /// The hit objects generated. - private IEnumerable generateSpecific(HitObject original) + private IEnumerable generateSpecific(HitObject original, Beatmap originalBeatmap) { - var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern); + var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); Pattern newPattern = generator.Generate(); lastPattern = newPattern; @@ -125,26 +126,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// Method that generates hit objects for non-osu!mania beatmaps. /// /// The original hit object. + /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. /// The hit objects generated. - private IEnumerable generateConverted(HitObject original) + private IEnumerable generateConverted(HitObject original, Beatmap originalBeatmap) { var endTimeData = original as IHasEndTime; var distanceData = original as IHasDistance; var positionData = original as IHasPosition; - // Following lines currently commented out to appease resharper - Patterns.PatternGenerator conversion = null; if (distanceData != null) - conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern); + conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); else if (endTimeData != null) - conversion = new EndTimeObjectPatternGenerator(random, original, beatmap); + conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); else if (positionData != null) { computeDensity(original.StartTime); - conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair); + conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); recordNote(original.StartTime, positionData.Position); } @@ -153,10 +153,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return null; Pattern newPattern = conversion.Generate(); - lastPattern = newPattern; - var stairPatternGenerator = conversion as HitObjectPatternGenerator; - lastStair = stairPatternGenerator?.StairType ?? lastStair; + lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; + lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; return newPattern.HitObjects; } @@ -166,8 +165,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator { - public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) - : base(random, hitObject, beatmap, previousPattern) + public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index a102781e70..28cf119833 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -29,11 +30,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private PatternType convertType; - public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) - : base(random, hitObject, beatmap, previousPattern) + public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { convertType = PatternType.None; - if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) + if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) convertType = PatternType.LowProbability; var distanceData = hitObject as IHasDistance; @@ -305,19 +306,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p4 = 0; break; case 3: - p2 = Math.Max(p2, 0.1); + p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; break; case 4: - p2 = Math.Max(p2, 0.3); - p3 = Math.Max(p3, 0.04); + p2 = Math.Min(p2, 0.3); + p3 = Math.Min(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); + p2 = Math.Min(p2, 0.34); + p3 = Math.Min(p3, 0.1); + p4 = Math.Min(p4, 0.03); break; } @@ -396,17 +397,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // Create the hold note addToPattern(pattern, holdColumn, startTime, endTime); - int noteCount = 1; + int nextColumn = Random.Next(RandomStart, TotalColumns); + int noteCount; if (ConversionDifficulty > 6.5) noteCount = GetRandomNoteCount(0.63, 0); else if (ConversionDifficulty > 4) noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0); else if (ConversionDifficulty > 2.5) noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0); + else + noteCount = 0; noteCount = Math.Min(TotalColumns - 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, TotalColumns); var rowPattern = new Pattern(); for (int i = 0; i <= spanCount; i++) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 278a4c4aab..ffbabba75a 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using System.Linq; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy @@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { private readonly double endTime; - public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap) - : base(random, hitObject, beatmap, new Pattern()) + public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) { var endtimeData = HitObject as IHasEndTime; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index c4ef23a982..e126534c54 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using OpenTK; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Objects; @@ -19,8 +20,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) - : base(random, hitObject, beatmap, previousPattern) + public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); if (density < 0) throw new ArgumentOutOfRangeException(nameof(density)); @@ -308,20 +309,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p5 = 0; break; case 3: - p2 = Math.Max(p2, 0.1); + p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; p5 = 0; break; case 4: - p2 = Math.Max(p2, 0.23); - p3 = Math.Max(p3, 0.04); + p2 = Math.Min(p2, 0.23); + p3 = Math.Min(p3, 0.04); p4 = 0; p5 = 0; break; case 5: - p3 = Math.Max(p3, 0.15); - p4 = Math.Max(p4, 0.03); + p3 = Math.Min(p3, 0.15); + p4 = Math.Min(p4, 0.03); p5 = 0; break; } @@ -355,23 +356,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p3 = 0; break; case 3: - centreProbability = Math.Max(centreProbability, 0.03); - p2 = Math.Max(p2, 0.1); + centreProbability = Math.Min(centreProbability, 0.03); + p2 = 0; p3 = 0; break; case 4: centreProbability = 0; - p2 = Math.Max(p2 * 2, 0.2); + p2 = Math.Min(p2 * 2, 0.2); p3 = 0; break; case 5: - centreProbability = Math.Max(centreProbability, 0.03); + centreProbability = Math.Min(centreProbability, 0.03); p3 = 0; break; case 6: centreProbability = 0; - p2 = Math.Max(p2 * 2, 0.5); - p3 = Math.Max(p3 * 2, 0.15); + p2 = Math.Min(p2 * 2, 0.5); + p3 = Math.Min(p3 * 2, 0.15); break; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 5f98749f0c..501950cdcd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -25,14 +25,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// protected readonly FastRandom Random; - protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) + /// + /// The beatmap which is being converted from. + /// + protected readonly Beatmap OriginalBeatmap; + + protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) : base(hitObject, beatmap, previousPattern) { if (random == null) throw new ArgumentNullException(nameof(random)); - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); + if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); Random = random; + OriginalBeatmap = originalBeatmap; + RandomStart = TotalColumns == 8 ? 1 : 0; } @@ -94,17 +100,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (conversionDifficulty != null) return conversionDifficulty.Value; - HitObject lastObject = Beatmap.HitObjects.LastOrDefault(); - HitObject firstObject = Beatmap.HitObjects.FirstOrDefault(); + HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); + HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); - drainTime -= Beatmap.TotalBreakTime; + drainTime -= OriginalBeatmap.TotalBreakTime; if (drainTime == 0) - drainTime = 10000; + drainTime = 10000000; - BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + // We need this in seconds + drainTime /= 1000; + + BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; + conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); return conversionDifficulty.Value; diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index 75a8543548..f4e3d54a3d 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -4,18 +4,142 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using System; using System.Collections.Generic; namespace osu.Game.Rulesets.Mania { - public class ManiaDifficultyCalculator : DifficultyCalculator + internal class ManiaDifficultyCalculator : DifficultyCalculator { + private const double star_scaling_factor = 0.018; + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.9; + + /// + /// HitObjects are stored as a member variable. + /// + private readonly List difficultyHitObjects = new List(); + public ManiaDifficultyCalculator(Beatmap beatmap) : base(beatmap) { } - public override double Calculate(Dictionary categoryDifficulty = null) => 0; + public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods) + : base(beatmap, mods) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + // Fill our custom DifficultyHitObject class, that carries additional information + difficultyHitObjects.Clear(); + + int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7; + + foreach (var hitObject in Beatmap.HitObjects) + difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount)); + + // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. + difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); + + if (!calculateStrainValues()) + return 0; + + double starRating = calculateDifficulty() * star_scaling_factor; + + categoryDifficulty?.Add("Strain", starRating); + + return starRating; + } + + private bool calculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) + { + if (!hitObjectsEnumerator.MoveNext()) + return false; + + ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + var next = hitObjectsEnumerator.Current; + next?.CalculateStrains(current, TimeRate); + current = next; + } + + return true; + } + } + + private double calculateDifficulty() + { + double actualStrainStep = strain_step * TimeRate; + + // Find the highest strain value within each strain step + List highestStrains = new List(); + double intervalEndTime = actualStrainStep; + double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval + + ManiaHitObjectDifficulty previousHitObject = null; + foreach (var hitObject in difficultyHitObjects) + { + // While we are beyond the current interval push the currently available maximum to our strain list + while (hitObject.BaseHitObject.StartTime > intervalEndTime) + { + highestStrains.Add(maximumStrain); + + // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay + // until the beginning of the next interval. + if (previousHitObject == null) + { + maximumStrain = 0; + } + else + { + double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + double strain = hitObject.IndividualStrain + hitObject.OverallStrain; + maximumStrain = Math.Max(strain, maximumStrain); + + previousHitObject = hitObject; + } + + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } + + return difficulty; + } protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap); } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 16b6888f2b..e135e14001 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -91,6 +91,7 @@ namespace osu.Game.Rulesets.Mania }, new ManiaModRandom(), new ManiaModDualStages(), + new ManiaModMirror(), new MultiMod { Mods = new Mod[] @@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Mania public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap); + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); public override int? LegacyID => 3; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs new file mode 100644 index 0000000000..cfa5ef88b8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System.Linq; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModMirror : Mod, IApplicableToRulesetContainer + { + public override string Name => "Mirror"; + public override string ShortenedName => "MR"; + public override ModType Type => ModType.Special; + public override double ScoreMultiplier => 1; + public override bool Ranked => true; + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + + rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs new file mode 100644 index 0000000000..2b59279972 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using System; + +namespace osu.Game.Rulesets.Mania.Objects +{ + internal class ManiaHitObjectDifficulty + { + /// + /// Factor by how much individual / overall strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// + internal const double INDIVIDUAL_DECAY_BASE = 0.125; + internal const double OVERALL_DECAY_BASE = 0.30; + + internal ManiaHitObject BaseHitObject; + + private readonly int beatmapColumnCount; + + + private readonly double endTime; + private readonly double[] heldUntil; + + /// + /// Measures jacks or more generally: repeated presses of the same button + /// + private readonly double[] individualStrains; + + internal double IndividualStrain + { + get + { + return individualStrains[BaseHitObject.Column]; + } + + set + { + individualStrains[BaseHitObject.Column] = value; + } + } + + /// + /// Measures note density in a way + /// + internal double OverallStrain = 1; + + public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount) + { + BaseHitObject = baseHitObject; + + endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime; + + beatmapColumnCount = columnCount; + heldUntil = new double[beatmapColumnCount]; + individualStrains = new double[beatmapColumnCount]; + + for (int i = 0; i < beatmapColumnCount; ++i) + { + individualStrains[i] = 0; + heldUntil[i] = 0; + } + } + + internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate) + { + // TODO: Factor in holds + double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000); + double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000); + + double holdFactor = 1.0; // Factor to all additional strains in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + // Fill up the heldUntil array + for (int i = 0; i < beatmapColumnCount; ++i) + { + heldUntil[i] = previousHitObject.heldUntil[i]; + + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i]) + { + holdAddition = 1.0; + } + + // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 + if (endTime == heldUntil[i]) + { + holdAddition = 0; + } + + // We give a slight bonus to everything if something is held meanwhile + if (heldUntil[i] > endTime) + { + holdFactor = 1.25; + } + + // Decay individual strains + individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; + } + + heldUntil[BaseHitObject.Column] = endTime; + + // Increase individual strain in own column + IndividualStrain += 2.0 * holdFactor; + + OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs index 2095addc72..9d55ab643d 100644 --- a/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests private bool isForCurrentRuleset; [NonParallelizable] - [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2150")] + [TestCase("basic", false)] public void Test(string name, bool isForCurrentRuleset) { this.isForCurrentRuleset = isForCurrentRuleset; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 8a03f5a785..f50f077c76 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -1,17 +1,25 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.UI { internal class DrawableManiaJudgement : DrawableJudgement { - public DrawableManiaJudgement(Judgement judgement) - : base(judgement) + public DrawableManiaJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) { - JudgementText.TextSize = 25; + } + + [BackgroundDependencyLoader] + private void load() + { + if (JudgementText != null) + JudgementText.TextSize = 25; } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 2b8039f5df..d4ca704829 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using OpenTK; using OpenTK.Graphics; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Container content; public Container Judgements => judgements; - private readonly Container judgements; + private readonly JudgementContainer judgements; private readonly Container topLevelContainer; @@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.UI Padding = new MarginPadding { Top = HIT_TARGET_POSITION } } }, - judgements = new Container + judgements = new JudgementContainer { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, @@ -171,7 +172,7 @@ namespace osu.Game.Rulesets.Mania.UI internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { judgements.Clear(); - judgements.Add(new DrawableManiaJudgement(judgement) + judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index a09b3e93a7..39b856b67b 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -81,6 +81,7 @@ + @@ -114,6 +115,7 @@ + diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 2de8c2f64e..586b516a11 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; @@ -14,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays public class SliderCircleMask : HitObjectMask { public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider) - : this(sliderHead, sliderHead.Position, slider) + : this(sliderHead, Vector2.Zero, slider) { } public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider) - : this(sliderTail, sliderTail.Position, slider) + : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) { } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 716f4b629b..1468c82b57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -2,24 +2,24 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Judgements; using OpenTK; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuJudgement : DrawableJudgement { - public DrawableOsuJudgement(OsuJudgement judgement) - : base(judgement) + public DrawableOsuJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) { } protected override void LoadComplete() { if (Judgement.Result != HitResult.Miss) - JudgementText.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.LoadComplete(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 9be951e29c..76ed89be67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; using OpenTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -19,15 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Blending = BlendingMode.Additive; Alpha = 0; - Children = new Drawable[] + Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece { - new TrianglesPiece - { - Blending = BlendingMode.Additive, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - } - }; + Blending = BlendingMode.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, false); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 56faa335b1..921d24f69d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; using osu.Framework.Graphics.Shapes; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -14,22 +15,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Size = new Vector2(128); - Masking = true; - CornerRadius = Size.X / 2; - Anchor = Anchor.Centre; Origin = Anchor.Centre; Blending = BlendingMode.Additive; Alpha = 0; - Children = new Drawable[] + Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer { - new Box + Masking = true, + RelativeSizeAxes = Axes.Both, + Child = new Box { RelativeSizeAxes = Axes.Both } - }; + }, false); } } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs new file mode 100644 index 0000000000..a11f32935e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseEditor : EditorTestCase + { + public TestCaseEditor() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 7f8cbce78e..98a8096678 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI public class OsuPlayfield : Playfield { private readonly Container approachCircles; - private readonly Container judgementLayer; + private readonly JudgementContainer judgementLayer; private readonly ConnectionRenderer connectionLayer; // Todo: This should not be a thing, but is currently required for the editor @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.UI RelativeSizeAxes = Axes.Both, Depth = 2, }, - judgementLayer = new Container + judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both, Depth = 1, @@ -75,16 +75,13 @@ namespace osu.Game.Rulesets.Osu.UI private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) { - var osuJudgement = (OsuJudgement)judgement; - var osuObject = (OsuHitObject)judgedObject.HitObject; - if (!judgedObject.DisplayJudgement) return; - DrawableOsuJudgement explosion = new DrawableOsuJudgement(osuJudgement) + DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) { Origin = Anchor.Centre, - Position = osuObject.StackedEndPosition + osuJudgement.PositionOffset + Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset }; judgementLayer.Add(explosion); diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index b8f56c1f5d..4a404e9526 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -130,6 +130,7 @@ + diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index c0e8bd1b5a..6274232ffd 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -15,17 +15,14 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrawableTaikoJudgement : DrawableJudgement { - public readonly DrawableHitObject JudgedObject; - /// /// Creates a new judgement text. /// /// The object which is being judged. /// The judgement to visualise. - public DrawableTaikoJudgement(DrawableHitObject judgedObject, Judgement judgement) - : base(judgement) + public DrawableTaikoJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) { - JudgedObject = judgedObject; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 49c87f7480..75aaceaecb 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -16,6 +16,7 @@ using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; - private readonly Container judgementContainer; + private readonly JudgementContainer judgementContainer; protected override Container Content => content; private readonly Container content; @@ -131,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.UI Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, Blending = BlendingMode.Additive }, - judgementContainer = new Container + judgementContainer = new JudgementContainer { Name = "Judgements", RelativeSizeAxes = Axes.Y, @@ -227,7 +228,7 @@ namespace osu.Game.Rulesets.Taiko.UI { if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null) { - judgementContainer.Add(new DrawableTaikoJudgement(judgedObject, judgement) + judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject) { Anchor = judgement.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, Origin = judgement.IsHit ? Anchor.BottomCentre : Anchor.Centre, diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ab10da2cd1..b74be134c1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmap.Metadata; @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo; + var beatmapInfo = decoder.Decode(stream).BeatmapInfo; int[] expectedBookmarks = { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmap.Metadata; @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty; + var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var metadata = beatmap.Metadata; var breakPoint = beatmap.Breaks[0]; @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var comboColors = decoder.DecodeBeatmap(stream).ComboColors; + var comboColors = decoder.Decode(stream).ComboColors; Color4[] expectedColors = { @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var hitObjects = decoder.DecodeBeatmap(stream).HitObjects; + var hitObjects = decoder.Decode(stream).HitObjects; var curveData = hitObjects[0] as IHasCurve; var positionData = hitObjects[0] as IHasPosition; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index dce6c0f55b..1c0801c634 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -18,11 +18,11 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeStoryboardEvents() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyStoryboardDecoder(); using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { - var storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(stream); + var storyboard = decoder.Decode(stream); Assert.IsTrue(storyboard.HasDrawable); Assert.AreEqual(4, storyboard.Layers.Count()); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 89d96c774e..80dea9d01d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { - var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.DecodeBeatmap(sr); + var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2)); + return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2)); } } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 1f7246a119..29d25accbb 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps.IO BeatmapMetadata meta; using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) - meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; + meta = Decoder.GetDecoder(stream).Decode(stream).Metadata; Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index fe26366362..c68e548f44 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -22,6 +23,7 @@ namespace osu.Game.Tests.Visual public class TestCaseBeatmapCarousel : OsuTestCase { private TestBeatmapCarousel carousel; + private RulesetStore rulesets; public override IReadOnlyList RequiredTypes => new[] { @@ -46,8 +48,10 @@ namespace osu.Game.Tests.Visual private const int set_count = 5; [BackgroundDependencyLoader] - private void load() + private void load(RulesetStore rulesets) { + this.rulesets = rulesets; + Add(carousel = new TestBeatmapCarousel { RelativeSizeAxes = Axes.Both, @@ -75,6 +79,7 @@ namespace osu.Game.Tests.Visual testRemoveAll(); testEmptyTraversal(); testHiding(); + testSelectingFilteredRuleset(); } private void ensureRandomFetchSuccess() => @@ -363,6 +368,41 @@ namespace osu.Game.Tests.Visual } } + private void testSelectingFilteredRuleset() + { + var testMixed = createTestBeatmapSet(set_count + 1); + AddStep("add mixed ruleset beatmapset", () => + { + for (int i = 0; i <= 2; i++) + { + testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); + testMixed.Beatmaps[i].RulesetID = i; + } + + carousel.UpdateBeatmapSet(testMixed); + }); + AddStep("filter to ruleset 0", () => + carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); + AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0])); + + AddStep("remove mixed set", () => + { + carousel.RemoveBeatmapSet(testMixed); + testMixed = null; + }); + var testSingle = createTestBeatmapSet(set_count + 2); + testSingle.Beatmaps.ForEach(b => + { + b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); + b.RulesetID = b.Ruleset.ID ?? 1; + }); + AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false)); + checkNoSelection(); + AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); + } + private BeatmapSetInfo createTestBeatmapSet(int id) { return new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs index ae24d86325..51b8c61963 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs @@ -3,9 +3,9 @@ using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; -using osu.Game.Screens.Play.BreaksOverlay; using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditor.cs b/osu.Game.Tests/Visual/TestCaseEditor.cs deleted file mode 100644 index c626ca8e7f..0000000000 --- a/osu.Game.Tests/Visual/TestCaseEditor.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Screens; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditor : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; - - private readonly Random rng; - - private BeatmapManager beatmaps; - private OsuGameBase osuGame; - - public TestCaseEditor() - { - rng = new Random(1337); - - Add(new Editor()); - AddStep("Next beatmap", nextBeatmap); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, BeatmapManager beatmaps) - { - this.osuGame = osuGame; - this.beatmaps = beatmaps; - } - - private void nextBeatmap() - { - var sets = beatmaps.GetAllUsableBeatmapSets(); - if (sets.Count == 0) - return; - - BeatmapInfo info = sets[rng.Next(0, sets.Count)].Beatmaps[0]; - osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(info); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 3caef777e7..1fc6c6f224 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Visual checkSupporterTag(false); + AddStep("Show null dummy", () => profile.ShowUser(new User + { + Username = @"Null", + Id = 1, + }, false)); + AddStep("Show ppy", () => profile.ShowUser(new User { Username = @"peppy", diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 1cfa7bc111..ed9580211b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -126,7 +126,6 @@ - diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4fd54e4364..9b00993b6e 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -22,6 +22,7 @@ namespace osu.Game.Beatmaps public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); + public List ComboColors = new List { new Color4(17, 136, 170, 255), @@ -85,9 +86,13 @@ namespace osu.Game.Beatmaps /// Constructs a new beatmap. /// /// The original beatmap to use the parameters of. - public Beatmap(Beatmap original = null) + public Beatmap(Beatmap original) : base(original) { } + + public Beatmap() + { + } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1d6d8b6726..817a3388e2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -301,7 +301,7 @@ namespace osu.Game.Beatmaps BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) - metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; + metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata; return new BeatmapSetInfo { @@ -328,8 +328,8 @@ namespace osu.Game.Beatmaps raw.CopyTo(ms); ms.Position = 0; - var decoder = Decoder.GetDecoder(sr); - Beatmap beatmap = decoder.DecodeBeatmap(sr); + var decoder = Decoder.GetDecoder(sr); + Beatmap beatmap = decoder.Decode(sr); beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a72c1adfcd..fb11684309 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Graphics.Textures; using osu.Game.Storyboards; @@ -30,10 +31,7 @@ namespace osu.Game.Beatmaps try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) - { - Decoder decoder = Decoder.GetDecoder(stream); - return decoder.DecodeBeatmap(stream); - } + return Decoder.GetDecoder(stream).Decode(stream); } catch { @@ -78,23 +76,23 @@ namespace osu.Game.Beatmaps Storyboard storyboard; try { - using (var beatmap = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { - Decoder decoder = Decoder.GetDecoder(beatmap); + var decoder = Decoder.GetDecoder(stream); // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (BeatmapSetInfo?.StoryboardFile == null) - storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap); + storyboard = decoder.Decode(stream); else { - using (var reader = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) - storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap, reader); + using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + storyboard = decoder.Decode(stream, secondaryStream); } } } - catch + catch (Exception e) { + Logger.Error(e, "Storyboard failed to load"); storyboard = new Storyboard(); } diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 39817df6a6..2bea31c0d3 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -24,9 +24,15 @@ namespace osu.Game.Beatmaps protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null) { - Beatmap = CreateBeatmapConverter(beatmap).Convert(beatmap); Mods = mods ?? new Mod[0]; + var converter = CreateBeatmapConverter(beatmap); + + foreach (var mod in Mods.OfType>()) + mod.ApplyToBeatmapConverter(converter); + + Beatmap = converter.Convert(beatmap); + ApplyMods(Mods); PreprocessHitObjects(); diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 1aae52208a..9f10485c5f 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -4,38 +4,64 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Storyboards; +using System.Linq; namespace osu.Game.Beatmaps.Formats { + public abstract class Decoder : Decoder + where TOutput : new() + { + protected virtual TOutput CreateTemplateObject() => new TOutput(); + + public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) + { + var output = CreateTemplateObject(); + foreach (StreamReader stream in new[] { primaryStream }.Concat(otherStreams)) + ParseStreamInto(stream, output); + return output; + } + + protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); + } + public abstract class Decoder { - private static readonly Dictionary> decoders = new Dictionary>(); + private static readonly Dictionary>> decoders = new Dictionary>>(); static Decoder() { - LegacyDecoder.Register(); + LegacyBeatmapDecoder.Register(); JsonBeatmapDecoder.Register(); + LegacyStoryboardDecoder.Register(); } /// /// Retrieves a to parse a . /// /// A stream pointing to the . - public static Decoder GetDecoder(StreamReader stream) + public static Decoder GetDecoder(StreamReader stream) + where T : new() { if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + throw new IOException(@"Unknown decoder type"); + string line; do - { line = stream.ReadLine()?.Trim(); } - while (line != null && line.Length == 0); + { + line = stream.ReadLine()?.Trim(); + } while (line != null && line.Length == 0); - if (line == null || !decoders.ContainsKey(line)) + if (line == null) throw new IOException(@"Unknown file format"); - return decoders[line](line); + var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault(); + if (decoder == null) + throw new IOException(@"Unknown file format"); + + return (Decoder)decoder.Invoke(line); } /// @@ -43,41 +69,12 @@ namespace osu.Game.Beatmaps.Formats /// /// A string in the file which triggers this decoder to be used. /// A function which constructs the given . - protected static void AddDecoder(string magic, Func constructor) + protected static void AddDecoder(string magic, Func constructor) { - decoders[magic] = constructor; + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + decoders.Add(typeof(T), typedDecoders = new Dictionary>()); + + typedDecoders[magic] = constructor; } - - /// - /// Retrieves a to parse a - /// - public abstract Decoder GetStoryboardDecoder(); - - public virtual Beatmap DecodeBeatmap(StreamReader stream) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata(), - BaseDifficulty = new BeatmapDifficulty(), - }, - }; - - ParseBeatmap(stream, beatmap); - return beatmap; - } - - protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap); - - public virtual Storyboard DecodeStoryboard(params StreamReader[] streams) - { - var storyboard = new Storyboard(); - foreach (StreamReader stream in streams) - ParseStoryboard(stream, storyboard); - return storyboard; - } - - protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard); } } diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index b0798e5a87..add0f39280 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -3,20 +3,17 @@ using System.IO; using osu.Game.IO.Serialization; -using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class JsonBeatmapDecoder : Decoder + public class JsonBeatmapDecoder : Decoder { public static void Register() { - AddDecoder("{", m => new JsonBeatmapDecoder()); + AddDecoder("{", m => new JsonBeatmapDecoder()); } - public override Decoder GetStoryboardDecoder() => this; - - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) { stream.BaseStream.Position = 0; stream.DiscardBufferedData(); @@ -26,10 +23,5 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); } - - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) - { - // throw new System.NotImplementedException(); - } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 7d4f8b5bf5..915ea9b587 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.IO; +using System.Linq; using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; @@ -12,8 +13,10 @@ using osu.Framework; namespace osu.Game.Beatmaps.Formats { - public class LegacyBeatmapDecoder : LegacyDecoder + public class LegacyBeatmapDecoder : LegacyDecoder { + public const int LATEST_VERSION = 14; + private Beatmap beatmap; private bool hasCustomColours; @@ -22,6 +25,11 @@ namespace osu.Game.Beatmaps.Formats private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; + public static void Register() + { + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + } + /// /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. @@ -35,29 +43,18 @@ namespace osu.Game.Beatmaps.Formats private readonly int offset = UniversalOffset; - public LegacyBeatmapDecoder() + public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) { - } - - public LegacyBeatmapDecoder(string header) - { - BeatmapVersion = int.Parse(header.Substring(17)); - // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - offset += BeatmapVersion < 5 ? 24 : 0; + offset += FormatVersion < 5 ? 24 : 0; } - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - this.beatmap = beatmap; - this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion; + this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - ParseContent(stream); + base.ParseStreamInto(stream, beatmap); // objects may be out of order *only* if a user has manually edited an .osu file. // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). @@ -67,14 +64,9 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); } - protected override bool ShouldSkipLine(string line) - { - if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_")) - return true; - return false; - } + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"); - protected override void ProcessSection(Section section, string line) + protected override void ParseLine(Beatmap beatmap, Section section, string line) { switch (section) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index e0fc439924..6a3fb82586 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -4,47 +4,20 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public abstract class LegacyDecoder : Decoder + public abstract class LegacyDecoder : Decoder + where T : new() { - public static void Register() + protected readonly int FormatVersion; + + protected LegacyDecoder(int version) { - AddDecoder(@"osu file format v14", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v13", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v12", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v11", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v10", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v9", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v8", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v7", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v6", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v5", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v4", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v3", m => new LegacyBeatmapDecoder(m)); - // TODO: differences between versions + FormatVersion = version; } - protected int BeatmapVersion; - - public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion); - - public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream)); - - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) - { - throw new NotImplementedException(); - } - - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) - { - throw new NotImplementedException(); - } - - protected void ParseContent(StreamReader stream) + protected override void ParseStreamInto(StreamReader stream, T beatmap) { Section section = Section.None; @@ -54,13 +27,6 @@ namespace osu.Game.Beatmaps.Formats if (ShouldSkipLine(line)) continue; - // It's already set in ParseBeatmap... why do it again? - //if (line.StartsWith(@"osu file format v")) - //{ - // Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17)); - // continue; - //} - if (line.StartsWith(@"[") && line.EndsWith(@"]")) { if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) @@ -68,18 +34,13 @@ namespace osu.Game.Beatmaps.Formats continue; } - ProcessSection(section, line); + ParseLine(beatmap, section, line); } } - protected virtual bool ShouldSkipLine(string line) - { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//")) - return true; - return false; - } + protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); - protected abstract void ProcessSection(Section section, string line); + protected abstract void ParseLine(T output, Section section, string line); protected KeyValuePair SplitKeyVal(string line, char separator) { diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a4ff060c83..e35276ae1a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using OpenTK; @@ -13,37 +13,34 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class LegacyStoryboardDecoder : LegacyDecoder + public class LegacyStoryboardDecoder : LegacyDecoder { - private Storyboard storyboard; - private StoryboardSprite storyboardSprite; private CommandTimelineGroup timelineGroup; + private Storyboard storyboard; + private readonly Dictionary variables = new Dictionary(); public LegacyStoryboardDecoder() + : base(0) { } - public LegacyStoryboardDecoder(int beatmapVersion) + public static void Register() { - BeatmapVersion = beatmapVersion; + // note that this isn't completely correct + AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); + AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); } - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) + protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (storyboard == null) - throw new ArgumentNullException(nameof(storyboard)); - this.storyboard = storyboard; - - ParseContent(stream); + base.ParseStreamInto(stream, storyboard); } - protected override void ProcessSection(Section section, string line) + protected override void ParseLine(Storyboard storyboard, Section section, string line) { switch (section) { @@ -80,38 +77,38 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case EventType.Sprite: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); - storyboard.GetLayer(layer).Add(storyboardSprite); - } + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); + storyboard.GetLayer(layer).Add(storyboardSprite); + } break; case EventType.Animation: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - var frameCount = int.Parse(split[6]); - var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); - var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; - storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); - storyboard.GetLayer(layer).Add(storyboardSprite); - } + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + var frameCount = int.Parse(split[6]); + var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); + var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; + storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); + storyboard.GetLayer(layer).Add(storyboardSprite); + } break; case EventType.Sample: - { - var time = double.Parse(split[1], CultureInfo.InvariantCulture); - var layer = parseLayer(split[2]); - var path = cleanFilename(split[3]); - var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; - storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } + { + var time = double.Parse(split[1], CultureInfo.InvariantCulture); + var layer = parseLayer(split[2]); + var path = cleanFilename(split[3]); + var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; + storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); + } break; } } @@ -124,120 +121,120 @@ namespace osu.Game.Beatmaps.Formats switch (commandType) { case "T": - { - var triggerName = split[1]; - var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; - var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; - var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; - timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); - } + { + var triggerName = split[1]; + var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; + var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; + var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; + timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); + } break; case "L": - { - var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); - var loopCount = int.Parse(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); - } + { + var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); + var loopCount = int.Parse(split[2]); + timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); + } break; default: + { + if (string.IsNullOrEmpty(split[3])) + split[3] = split[2]; + + var easing = (Easing)int.Parse(split[1]); + var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); + var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); + + switch (commandType) { - if (string.IsNullOrEmpty(split[3])) - split[3] = split[2]; - - var easing = (Easing)int.Parse(split[1]); - var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); - var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); - - switch (commandType) + case "F": { - case "F": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "S": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); - } - break; - case "V": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); - } - break; - case "R": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); - } - break; - case "M": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); - timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); - } - break; - case "MX": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "MY": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "C": - { - var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); - var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); - var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); - var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; - var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; - var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; - timelineGroup?.Colour.Add(easing, startTime, endTime, - new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), - new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); - } - break; - case "P": - { - var type = split[4]; - switch (type) - { - case "A": - timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); - break; - case "H": - timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); - break; - case "V": - timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); - break; - } - } - break; - default: - throw new InvalidDataException($@"Unknown command type: {commandType}"); + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } + break; + case "S": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); + } + break; + case "V": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); + } + break; + case "R": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); + } + break; + case "M": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); + timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); + } + break; + case "MX": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "MY": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "C": + { + var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); + var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); + var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); + var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; + var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; + var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; + timelineGroup?.Colour.Add(easing, startTime, endTime, + new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), + new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); + } + break; + case "P": + { + var type = split[4]; + switch (type) + { + case "A": + timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); + break; + case "H": + timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); + break; + case "V": + timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); + break; + } + } + break; + default: + throw new InvalidDataException($@"Unknown command type: {commandType}"); } + } break; } } @@ -269,6 +266,7 @@ namespace osu.Game.Beatmaps.Formats case LegacyOrigins.BottomRight: return Anchor.BottomRight; } + throw new InvalidDataException($@"Unknown origin: {value}"); } diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index febe52d775..97d6225534 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -66,8 +66,10 @@ namespace osu.Game.Graphics.Containers { Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; - content.Position = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), content.Position, offset, 0, 1000, Easing.OutQuint); - content.Scale = new Vector2(1 + ParallaxAmount); + double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); + + content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); + content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint); } firstUpdate = false; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs new file mode 100644 index 0000000000..a912f989e0 --- /dev/null +++ b/osu.Game/Graphics/DrawableDate.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public class DrawableDate : OsuSpriteText, IHasTooltip + { + private readonly DateTimeOffset date; + private ScheduledDelegate updateTask; + + public DrawableDate(DateTimeOffset date) + { + AutoSizeAxes = Axes.Both; + Font = "Exo2.0-RegularItalic"; + + this.date = date.ToLocalTime(); + } + + [BackgroundDependencyLoader] + private void load() + { + updateTime(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Scheduler.Add(updateTimeWithReschedule); + } + + private void updateTimeWithReschedule() + { + updateTime(); + + var diffToNow = DateTimeOffset.Now.Subtract(date); + + double timeUntilNextUpdate = 1000; + if (diffToNow.TotalSeconds > 60) + { + timeUntilNextUpdate *= 60; + if (diffToNow.TotalMinutes > 60) + { + timeUntilNextUpdate *= 60; + + if (diffToNow.TotalHours > 24) + timeUntilNextUpdate *= 24; + } + } + + Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); + } + + public override bool HandleMouseInput => true; + + private void updateTime() => Text = date.Humanize(); + public string TooltipText => date.ToString(); + } +} diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1325179e0d..bab53cb462 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -125,7 +125,6 @@ namespace osu.Game.Online.API userReq.Success += u => { LocalUser.Value = u; - Username = LocalUser.Value.Username; failureCount = 0; //we're connected! diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index d085800f41..f4b363cd91 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -130,11 +130,7 @@ namespace osu.Game.Overlays.Profile } } }, - infoTextLeft = new OsuTextFlowContainer(t => - { - t.TextSize = 14; - t.Alpha = 0.8f; - }) + infoTextLeft = new OsuTextFlowContainer(t => t.TextSize = 14) { X = UserProfileOverlay.CONTENT_X_MARGIN, Y = cover_height + 20, @@ -318,11 +314,23 @@ namespace osu.Game.Overlays.Profile colourBar.Show(); } - void boldItalic(SpriteText t) + void boldItalic(SpriteText t) => t.Font = @"Exo2.0-BoldItalic"; + void lightText(SpriteText t) => t.Alpha = 0.8f; + + OsuSpriteText createScoreText(string text) => new OsuSpriteText { - t.Font = @"Exo2.0-BoldItalic"; - t.Alpha = 1; - } + TextSize = 14, + Text = text + }; + + OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = text + }; if (user.Age != null) { @@ -331,7 +339,7 @@ namespace osu.Game.Overlays.Profile if (user.Country != null) { - infoTextLeft.AddText("from "); + infoTextLeft.AddText("from ", lightText); infoTextLeft.AddText(user.Country.FullName, boldItalic); countryFlag.Country = user.Country; } @@ -344,18 +352,18 @@ namespace osu.Game.Overlays.Profile } else { - infoTextLeft.AddText("Joined "); - infoTextLeft.AddText(user.JoinDate.LocalDateTime.ToShortDateString(), boldItalic); + infoTextLeft.AddText("Joined ", lightText); + infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic); } infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen "); - infoTextLeft.AddText(user.LastVisit.LocalDateTime.ToShortDateString(), boldItalic); + infoTextLeft.AddText("Last seen ", lightText); + infoTextLeft.AddText(new DrawableDate(user.LastVisit), boldItalic); infoTextLeft.NewParagraph(); if (user.PlayStyle?.Length > 0) { - infoTextLeft.AddText("Plays with "); + infoTextLeft.AddText("Plays with ", lightText); infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); } @@ -411,23 +419,6 @@ namespace osu.Game.Overlays.Profile } } - // These could be local functions when C# 7 enabled - - private OsuSpriteText createScoreText(string text) => new OsuSpriteText - { - TextSize = 14, - Text = text - }; - - private OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) { if (string.IsNullOrEmpty(str)) return; @@ -436,10 +427,12 @@ namespace osu.Game.Overlays.Profile if (url != null) { infoTextRight.AddLink(" " + str, url); - } else + } + else { infoTextRight.AddText(" " + str); } + infoTextRight.NewLine(); } diff --git a/osu.Game/Overlays/Profile/RankGraph.cs b/osu.Game/Overlays/Profile/RankGraph.cs index 429049c7bc..369bdee65f 100644 --- a/osu.Game/Overlays/Profile/RankGraph.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Profile { placeholder.FadeIn(fade_duration, Easing.Out); - if (user == null) + if (user?.Statistics?.Ranks.Global == null) { rankText.Text = string.Empty; performanceText.Text = string.Empty; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Profile return; } - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Ranks.Global }; + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Ranks.Global.Value }; ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); if (ranks.Length > 1) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index bb1a409f2e..509356ae04 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -54,12 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks RightFlowContainer.SetLayoutPosition(text, 1); LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); - LeftFlowContainer.Add(new OsuSpriteText - { - Text = Score.Date.LocalDateTime.ToShortDateString(), - TextSize = 11, - Colour = OsuColour.Gray(0xAA), - }); + LeftFlowContainer.Add(new DrawableDate(Score.Date)); foreach (Mod mod in Score.Mods) modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 2dde8a3d54..e8be8d1e44 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; @@ -40,14 +39,12 @@ namespace osu.Game.Overlays.Profile.Sections.Recent RelativeSizeAxes = Axes.X, }); - RightFlowContainer.Add(new OsuSpriteText + RightFlowContainer.Add(new DrawableDate(activity.CreatedAt) { - Text = activity.CreatedAt.LocalDateTime.ToShortDateString(), + TextSize = 13, + Colour = OsuColour.Gray(0xAA), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Font = "Exo2.0-RegularItalic", - TextSize = 12, - Colour = OsuColour.Gray(0xAA), }); var formatted = createMessage(); diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index c2dfea9a08..16586adc0c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Toolbar avatar.User = new User(); break; case APIState.Online: - Text = api.Username; + Text = api.LocalUser.Value.Username; avatar.User = api.LocalUser; break; } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index f3fd7aeac5..aed0a6d7c6 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -169,15 +169,18 @@ namespace osu.Game.Overlays { Header.User = user; - foreach (string id in user.ProfileOrder) + if (user.ProfileOrder != null) { - var sec = sections.FirstOrDefault(s => s.Identifier == id); - if (sec != null) + foreach (string id in user.ProfileOrder) { - sec.User.Value = user; + var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) + { + sec.User.Value = user; - sectionsContainer.Add(sec); - tabs.AddItem(sec); + sectionsContainer.Add(sec); + tabs.AddItem(sec); + } } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index c1bf55b214..0f0c43bc88 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -9,7 +9,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Judgements { @@ -18,43 +21,37 @@ namespace osu.Game.Rulesets.Judgements /// public class DrawableJudgement : Container { + private const float judgement_size = 80; + protected readonly Judgement Judgement; - protected readonly SpriteText JudgementText; + public readonly DrawableHitObject JudgedObject; + + protected SpriteText JudgementText; /// /// Creates a drawable which visualises a . /// /// The judgement to visualise. - public DrawableJudgement(Judgement judgement) + public DrawableJudgement(Judgement judgement, DrawableHitObject judgedObject) { Judgement = judgement; + JudgedObject = judgedObject; - AutoSizeAxes = Axes.Both; - - Children = new[] - { - JudgementText = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Text = judgement.Result.GetDescription().ToUpper(), - Font = @"Venera", - Scale = new Vector2(0.85f, 1), - TextSize = 12 - } - }; + Size = new Vector2(judgement_size); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - switch (Judgement.Result) + Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText { - case HitResult.Miss: - Colour = colours.Red; - break; - } + Text = Judgement.Result.GetDescription().ToUpper(), + Font = @"Venera", + Colour = Judgement.Result == HitResult.Miss ? colours.Red : Color4.White, + Scale = new Vector2(0.85f, 1), + TextSize = 12 + }, restrictSize: false); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index ce292ef223..5084b28cf2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -188,8 +188,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); - var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]); + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); // Let's not implement this for now, because this doesn't fit nicely into the bank structure //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 92fbf25f04..e621c3cf2b 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); //add all legacy modes in correct order - foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) + foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) { if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null) context.RulesetInfo.Add(r.RulesetInfo); @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets context.SaveChanges(); //add any other modes - foreach (var r in instances.Where(r => r.LegacyID < 0)) + foreach (var r in instances.Where(r => r.LegacyID == null)) if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) context.RulesetInfo.Add(r.RulesetInfo); diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs index ba16d78b37..c047a421fd 100644 --- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs @@ -2,7 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring @@ -23,9 +25,15 @@ namespace osu.Game.Rulesets.Scoring protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) { - Beatmap = CreateBeatmapConverter().Convert(beatmap); Score = score; + var converter = CreateBeatmapConverter(); + + foreach (var mod in score.Mods.OfType>()) + mod.ApplyToBeatmapConverter(converter); + + Beatmap = converter.Convert(beatmap); + var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); diffCalc.Calculate(attributes); } diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs new file mode 100644 index 0000000000..1291b9fc98 --- /dev/null +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.UI +{ + public class JudgementContainer : Container + where T : DrawableJudgement + { + public override void Add(T judgement) + { + if (judgement == null) throw new ArgumentNullException(nameof(judgement)); + + // remove any existing judgements for the judged object. + // this can be the case when rewinding. + RemoveAll(c => c.JudgedObject == judgement.JudgedObject); + + base.Add(judgement); + } + } +} diff --git a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs similarity index 92% rename from osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs rename to osu.Game/Screens/Play/Break/BlurredIcon.cs index 5395d7688e..6f47c97f89 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -1,13 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Graphics; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using OpenTK; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { public class BlurredIcon : BufferedContainer { diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs similarity index 78% rename from osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs rename to osu.Game/Screens/Play/Break/BreakArrows.cs index 9fdf90bd28..f2a60cdddf 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -1,18 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using OpenTK; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Beatmaps.Timing; +using OpenTK; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { - public class ArrowsOverlay : VisibilityContainer + public class BreakArrows : CompositeDrawable { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private const int glow_icon_size = 60; private const int glow_icon_blur_sigma = 10; private const float glow_icon_final_offset = 0.22f; @@ -29,10 +26,10 @@ namespace osu.Game.Screens.Play.BreaksOverlay private readonly BlurredIcon leftBlurredIcon; private readonly BlurredIcon rightBlurredIcon; - public ArrowsOverlay() + public BreakArrows() { RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + InternalChildren = new Drawable[] { leftGlowIcon = new GlowIcon { @@ -82,22 +79,22 @@ namespace osu.Game.Screens.Play.BreaksOverlay }; } - protected override void PopIn() + public void Show(double duration) { - leftGlowIcon.MoveToX(-glow_icon_final_offset, fade_duration, Easing.OutQuint); - rightGlowIcon.MoveToX(glow_icon_final_offset, fade_duration, Easing.OutQuint); + leftGlowIcon.MoveToX(-glow_icon_final_offset, duration, Easing.OutQuint); + rightGlowIcon.MoveToX(glow_icon_final_offset, duration, Easing.OutQuint); - leftBlurredIcon.MoveToX(-blurred_icon_final_offset, fade_duration, Easing.OutQuint); - rightBlurredIcon.MoveToX(blurred_icon_final_offset, fade_duration, Easing.OutQuint); + leftBlurredIcon.MoveToX(-blurred_icon_final_offset, duration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_icon_final_offset, duration, Easing.OutQuint); } - protected override void PopOut() + public void Hide(double duration) { - leftGlowIcon.MoveToX(-glow_icon_offscreen_offset, fade_duration, Easing.OutQuint); - rightGlowIcon.MoveToX(glow_icon_offscreen_offset, fade_duration, Easing.OutQuint); + leftGlowIcon.MoveToX(-glow_icon_offscreen_offset, duration, Easing.OutQuint); + rightGlowIcon.MoveToX(glow_icon_offscreen_offset, duration, Easing.OutQuint); - leftBlurredIcon.MoveToX(-blurred_icon_offscreen_offset, fade_duration, Easing.OutQuint); - rightBlurredIcon.MoveToX(blurred_icon_offscreen_offset, fade_duration, Easing.OutQuint); + leftBlurredIcon.MoveToX(-blurred_icon_offscreen_offset, duration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_icon_offscreen_offset, duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs similarity index 62% rename from osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs rename to osu.Game/Screens/Play/Break/BreakInfo.cs index d7ab4ff2e5..5e011903fe 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/Break/BreakInfo.cs @@ -1,24 +1,21 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; -using osu.Game.Beatmaps.Timing; +using OpenTK; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { - public class InfoContainer : VisibilityContainer + public class BreakInfo : Container { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; + public PercentageBreakInfoLine AccuracyDisplay; + public BreakInfoLine RankDisplay; + public BreakInfoLine GradeDisplay; - public PercentageInfoLine AccuracyDisplay; - public InfoLine RankDisplay; - public InfoLine GradeDisplay; - - public InfoContainer() + public BreakInfo() { AutoSizeAxes = Axes.Both; Child = new FillFlowContainer @@ -43,16 +40,13 @@ namespace osu.Game.Screens.Play.BreaksOverlay Direction = FillDirection.Vertical, Children = new Drawable[] { - AccuracyDisplay = new PercentageInfoLine("Accuracy"), - RankDisplay = new InfoLine("Rank"), - GradeDisplay = new InfoLine("Grade"), + AccuracyDisplay = new PercentageBreakInfoLine("Accuracy"), + RankDisplay = new BreakInfoLine("Rank"), + GradeDisplay = new BreakInfoLine("Grade"), }, } }, }; } - - protected override void PopIn() => this.FadeIn(fade_duration); - protected override void PopOut() => this.FadeOut(fade_duration); } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs similarity index 84% rename from osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs rename to osu.Game/Screens/Play/Break/BreakInfoLine.cs index b39eaf1c22..3d96bca1fa 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { - public class InfoLine : Container + public class BreakInfoLine : Container where T : struct { private const int margin = 2; @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay private readonly string prefix; - public InfoLine(string name, string prefix = @"") + public BreakInfoLine(string name, string prefix = @"") { this.prefix = prefix; @@ -71,9 +71,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay } } - public class PercentageInfoLine : InfoLine + public class PercentageBreakInfoLine : BreakInfoLine { - public PercentageInfoLine(string name, string prefix = "") : base(name, prefix) + public PercentageBreakInfoLine(string name, string prefix = "") : base(name, prefix) { } diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs similarity index 93% rename from osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs rename to osu.Game/Screens/Play/Break/GlowIcon.cs index bad9df2093..79b39a873a 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -1,13 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Containers; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using OpenTK; -using osu.Framework.Allocation; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { public class GlowIcon : Container { @@ -16,24 +16,24 @@ namespace osu.Game.Screens.Play.BreaksOverlay public override Vector2 Size { + get { return base.Size; } set { blurredIcon.Size = spriteIcon.Size = value; blurredIcon.ForceRedraw(); } - get { return base.Size; } } public Vector2 BlurSigma { - set { blurredIcon.BlurSigma = value; } get { return blurredIcon.BlurSigma; } + set { blurredIcon.BlurSigma = value; } } public FontAwesome Icon { - set { spriteIcon.Icon = blurredIcon.Icon = value; } get { return spriteIcon.Icon; } + set { spriteIcon.Icon = blurredIcon.Icon = value; } } public GlowIcon() diff --git a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs similarity index 77% rename from osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs rename to osu.Game/Screens/Play/Break/LetterboxOverlay.cs index f4c9362fff..21eb5ebea0 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs @@ -1,18 +1,16 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.Timing; +using OpenTK.Graphics; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { - public class LetterboxOverlay : VisibilityContainer + public class LetterboxOverlay : CompositeDrawable { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; private const int height = 350; private static readonly Color4 transparent_black = new Color4(0, 0, 0, 0); @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay public LetterboxOverlay() { RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Container { @@ -48,8 +46,5 @@ namespace osu.Game.Screens.Play.BreaksOverlay } }; } - - protected override void PopIn() => this.FadeIn(fade_duration); - protected override void PopOut() => this.FadeOut(fade_duration); } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs similarity index 70% rename from osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs rename to osu.Game/Screens/Play/Break/RemainingTimeCounter.cs index 015fefb423..f6e683f519 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs @@ -1,18 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics; using System; -using osu.Game.Beatmaps.Timing; +using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play.Break { public class RemainingTimeCounter : Counter { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private readonly OsuSpriteText counter; public RemainingTimeCounter() @@ -25,13 +22,8 @@ namespace osu.Game.Screens.Play.BreaksOverlay TextSize = 33, Font = "Venera", }; - - Alpha = 0; } protected override void OnCountChanged(double count) => counter.Text = ((int)Math.Ceiling(count / 1000)).ToString(); - - public override void Show() => this.FadeIn(fade_duration); - public override void Hide() => this.FadeOut(fade_duration); } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs similarity index 51% rename from osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs rename to osu.Game/Screens/Play/BreakOverlay.cs index af7c1ef5aa..6c7ee596a1 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -1,15 +1,16 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; -using System.Collections.Generic; -using osu.Framework.Graphics.UserInterface; +using osu.Game.Screens.Play.Break; -namespace osu.Game.Screens.Play.BreaksOverlay +namespace osu.Game.Screens.Play { public class BreakOverlay : Container { @@ -18,28 +19,26 @@ namespace osu.Game.Screens.Play.BreaksOverlay private const int vertical_margin = 25; private List breaks; + + private readonly Container fadeContainer; + public List Breaks { + get => breaks; set { breaks = value; initializeBreaks(); } - get - { - return breaks; - } } public override bool RemoveCompletedTransforms => false; - private readonly bool letterboxing; - private readonly LetterboxOverlay letterboxOverlay; private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; - private readonly InfoContainer info; - private readonly ArrowsOverlay arrowsOverlay; + private readonly BreakInfo info; + private readonly BreakArrows breakArrows; public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) : this(letterboxing) @@ -49,61 +48,72 @@ namespace osu.Game.Screens.Play.BreaksOverlay public BreakOverlay(bool letterboxing) { - this.letterboxing = letterboxing; - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + Child = fadeContainer = new Container { - letterboxOverlay = new LetterboxOverlay + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - remainingTimeAdjustmentBox = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0, - Child = remainingTimeBox = new Container + new LetterboxOverlay + { + Alpha = letterboxing ? 1 : 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + remainingTimeAdjustmentBox = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Height = 8, - CornerRadius = 4, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } + Width = 0, + Child = remainingTimeBox = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 8, + CornerRadius = 4, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + }, + remainingTimeCounter = new RemainingTimeCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = vertical_margin }, + }, + info = new BreakInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = vertical_margin }, + }, + breakArrows = new BreakArrows + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } - }, - remainingTimeCounter = new RemainingTimeCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Margin = new MarginPadding { Bottom = vertical_margin }, - }, - info = new InfoContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = vertical_margin }, - }, - arrowsOverlay = new ArrowsOverlay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + initializeBreaks(); + } + private void initializeBreaks() { + if (!IsLoaded) return; // we need a clock. + FinishTransforms(true); Scheduler.CancelDelayedTasks(); - if (breaks == null) - return; + if (breaks == null) return; //we need breaks. foreach (var b in breaks) { @@ -112,6 +122,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay using (BeginAbsoluteSequence(b.StartTime, true)) { + fadeContainer.FadeIn(fade_duration); + breakArrows.Show(fade_duration); + remainingTimeAdjustmentBox .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) .Delay(b.Duration - fade_duration) @@ -123,37 +136,16 @@ namespace osu.Game.Screens.Play.BreaksOverlay .ResizeWidthTo(1); remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); - } - using (BeginAbsoluteSequence(b.StartTime)) - { - Schedule(showBreak); - using (BeginDelayedSequence(b.Duration - fade_duration)) - Schedule(hideBreak); + using (BeginDelayedSequence(b.Duration - fade_duration, true)) + { + fadeContainer.FadeOut(fade_duration); + breakArrows.Hide(fade_duration); + } } } } - private void showBreak() - { - if (letterboxing) - letterboxOverlay.Show(); - - remainingTimeCounter.Show(); - info.Show(); - arrowsOverlay.Show(); - } - - private void hideBreak() - { - if (letterboxing) - letterboxOverlay.Hide(); - - remainingTimeCounter.Hide(); - info.Hide(); - arrowsOverlay.Hide(); - } - private void bindProcessor(ScoreProcessor processor) { info.AccuracyDisplay.Current.BindTo(processor.Accuracy); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d24ed53518..c8ff261a93 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -25,7 +25,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play.BreaksOverlay; using osu.Game.Screens.Ranking; using osu.Game.Storyboards.Drawables; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9793440348..c2bb155753 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -169,20 +169,43 @@ namespace osu.Game.Screens.Select }); } - public void SelectBeatmap(BeatmapInfo beatmap) + /// + /// Selects a given beatmap on the carousel. + /// + /// If bypassFilters is false, we will try to select another unfiltered beatmap in the same set. If the + /// entire set is filtered, no selection is made. + /// + /// The beatmap to select. + /// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel). + /// True if a selection was made, False if it wasn't. + public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true) { if (beatmap?.Hidden != false) - return; + return false; - foreach (CarouselBeatmapSet group in beatmapSets) + foreach (CarouselBeatmapSet set in beatmapSets) { - var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + if (!bypassFilters && set.Filtered) + continue; + + var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + + if (item == null) + // The beatmap that needs to be selected doesn't exist in this set + continue; + + if (!bypassFilters && item.Filtered) + // The beatmap exists in this set but is filtered, so look for the first unfiltered map in the set + item = set.Beatmaps.FirstOrDefault(b => !b.Filtered); + if (item != null) { select(item); - return; + return true; } } + + return false; } /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 43d7cb637a..ca8a1cae41 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -214,11 +214,7 @@ namespace osu.Game.Screens.Select Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.TriggerChange(); - Beatmap.ValueChanged += b => - { - if (IsCurrentScreen) - Carousel.SelectBeatmap(b?.BeatmapInfo); - }; + Beatmap.ValueChanged += workingBeatmapChanged; } public void Edit(BeatmapInfo beatmap) @@ -261,6 +257,17 @@ namespace osu.Game.Screens.Select // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. private BeatmapInfo beatmapNoDebounce; + private void workingBeatmapChanged(WorkingBeatmap beatmap) + { + if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) + // 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; + Carousel.SelectBeatmap(beatmap.BeatmapInfo); + } + } + /// /// selection has been changed as the result of interaction with the carousel. /// @@ -450,16 +457,14 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false) + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) + return; + + if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom()) { - Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); - } - else if (Carousel.SelectedBeatmapSet == null) - { - if (!Carousel.SelectNextRandom()) - // in the case random selection failed, we want to trigger selectionChanged - // to show the dummy beatmap (we have nothing else to display). - carouselSelectionChanged(null); + // in the case random selection failed, we want to trigger selectionChanged + // to show the dummy beatmap (we have nothing else to display). + carouselSelectionChanged(null); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 17fe6369a7..5525cc483e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -29,15 +29,26 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(string componentName) { + switch (componentName) + { + case "Play/Miss": + componentName = "hit0"; + break; + case "Play/Meh": + componentName = "hit50"; + break; + case "Play/Good": + componentName = "hit100"; + break; + case "Play/Great": + componentName = "hit300"; + break; + } + var texture = textures.Get(componentName); if (texture == null) return null; - return new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Texture = texture, - }; + return new Sprite { Texture = texture }; } public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName); @@ -47,8 +58,14 @@ namespace osu.Game.Skinning private readonly SkinInfo skin; private readonly IResourceStore underlyingStore; - private string getPathForFile(string filename) => - skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + private string getPathForFile(string filename) + { + string lastPiece = filename.Split('/').Last(); + + var file = skin.Files.FirstOrDefault(f => + string.Equals(Path.GetFileNameWithoutExtension(f.Filename), lastPiece, StringComparison.InvariantCultureIgnoreCase)); + return file?.FileInfo.StoragePath; + } public LegacySkinResourceStore(SkinInfo skin, IResourceStore underlyingStore) { diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index cd669778a6..81abc9e80c 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -3,13 +3,14 @@ using System; using osu.Framework.Graphics; +using OpenTK; namespace osu.Game.Skinning { public class SkinnableDrawable : SkinnableDrawable { - public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true) - : base(name, defaultImplementation, fallback) + public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true, bool restrictSize = true) + : base(name, defaultImplementation, fallback, restrictSize) { } } @@ -21,10 +22,20 @@ namespace osu.Game.Skinning private readonly string componentName; - public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true) : base(fallback) + private readonly bool restrictSize; + + /// + /// + /// + /// The namespace-complete resource name for this skinnable element. + /// A function to create the default skin implementation of this element. + /// Whther to fallback to the default implementation when a custom skin is specified but not implementation is present. + /// Whether a user-skin drawable should be limited to the size of our parent. + public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true, bool restrictSize = true) : base(fallback) { componentName = name; createDefault = defaultImplementation; + this.restrictSize = restrictSize; RelativeSizeAxes = Axes.Both; } @@ -32,11 +43,25 @@ namespace osu.Game.Skinning protected override void SkinChanged(Skin skin, bool allowFallback) { var drawable = skin.GetDrawableComponent(componentName); - if (drawable == null && allowFallback) + if (drawable != null) + { + if (restrictSize) + { + drawable.RelativeSizeAxes = Axes.Both; + drawable.Size = Vector2.One; + drawable.FillMode = FillMode.Fit; + } + } + else if (allowFallback) drawable = createDefault(componentName); if (drawable != null) + { + drawable.Origin = Anchor.Centre; + drawable.Anchor = Anchor.Centre; + InternalChild = drawable; + } else ClearInternal(); } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 219d805bc1..8505498e4f 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -112,9 +112,9 @@ namespace osu.Game.Tests.Beatmaps using (var resStream = openResource($"{resource_namespace}.{name}.osu")) using (var stream = new StreamReader(resStream)) { - var decoder = Decoder.GetDecoder(stream); + var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; - return decoder.DecodeBeatmap(stream); + return decoder.Decode(stream); } } diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs new file mode 100644 index 0000000000..7dc6079959 --- /dev/null +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -0,0 +1,720 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Text; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using Decoder = osu.Game.Beatmaps.Formats.Decoder; + +namespace osu.Game.Tests.Beatmaps +{ + public class TestBeatmap : Beatmap + { + public TestBeatmap(RulesetInfo ruleset) + : base(createTestBeatmap()) + { + BeatmapInfo.Ruleset = ruleset; + } + + private static Beatmap createTestBeatmap() + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) + using (var reader = new StreamReader(stream)) + return Decoder.GetDecoder(reader).Decode(reader); + } + + private const string test_beatmap_data = +@"osu file format v14 + +[General] +AudioLeadIn: 500 +PreviewTime: 53498 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 0 +LetterboxInBreaks: 0 +WidescreenStoryboard: 1 + +[Editor] +DistanceSpacing: 1.2 +BeatDivisor: 4 +GridSize: 4 +TimelineZoom: 1 + +[Metadata] +Title:My Love +TitleUnicode:My Love +Artist:Kuba Oms +ArtistUnicode:Kuba Oms +Creator:W h i t e +Version:Hard +Source:ADHD +Tags:Monthly Beatmapping Contest Electronic folk pop w_h_i_t_e +BeatmapID:397534 +BeatmapSetID:163112 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:6 +ApproachRate:7 +SliderMultiplier:1.44 +SliderTickRate:2 + +[Events] +//Break Periods +2,69870,83770 +2,152170,158770 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +2170,468.75,4,2,0,40,1,0 +4045,-100,4,2,0,30,0,0 +4162,-100,4,2,0,40,0,0 +5920,-100,4,2,0,30,0,0 +6037,-100,4,2,0,40,0,0 +7795,-100,4,2,0,30,0,0 +7912,-100,4,2,0,40,0,0 +9670,-100,4,2,0,40,0,0 +9787,-100,4,2,0,50,0,0 +11545,-100,4,2,0,40,0,0 +11662,-100,4,2,0,50,0,0 +13420,-100,4,2,0,40,0,0 +13537,-100,4,2,0,50,0,0 +15295,-100,4,2,0,40,0,0 +15412,-100,4,2,0,50,0,0 +17170,-100,4,2,0,40,0,0 +17287,-100,4,2,0,50,0,0 +19045,-100,4,2,0,40,0,0 +19162,-100,4,2,0,50,0,0 +20920,-100,4,2,0,40,0,0 +21037,-100,4,2,0,50,0,0 +22795,-100,4,2,0,40,0,0 +22912,-100,4,2,0,50,0,0 +24670,-100,4,2,0,70,0,0 +37560,-200,4,2,0,30,0,0 +38263,-200,4,2,0,5,0,0 +38966,-100,4,2,0,30,0,0 +39670,-100,4,2,0,70,0,0 +53732,-100,4,2,0,40,0,0 +54670,-100,4,2,0,80,0,1 +55138,-100,4,2,0,60,0,1 +55255,-100,4,2,0,80,0,1 +56076,-100,4,2,0,60,0,1 +56193,-100,4,2,0,80,0,1 +57013,-100,4,2,0,60,0,1 +57130,-100,4,2,0,80,0,1 +57951,-100,4,2,0,60,0,1 +58068,-100,4,2,0,80,0,1 +58888,-100,4,2,0,60,0,1 +59005,-100,4,2,0,80,0,1 +59826,-100,4,2,0,60,0,1 +59943,-100,4,2,0,80,0,1 +60763,-100,4,2,0,60,0,1 +60880,-100,4,2,0,80,0,1 +61701,-100,4,2,0,60,0,1 +61818,-100,4,2,0,80,0,1 +62638,-100,4,2,0,60,0,1 +62755,-100,4,2,0,80,0,1 +63576,-100,4,2,0,60,0,1 +63693,-100,4,2,0,80,0,1 +64513,-100,4,2,0,60,0,1 +64630,-100,4,2,0,80,0,1 +65451,-100,4,2,0,60,0,1 +65568,-100,4,2,0,80,0,1 +66388,-100,4,2,0,60,0,1 +66505,-100,4,2,0,80,0,1 +67326,-100,4,2,0,60,0,1 +67443,-100,4,2,0,80,0,1 +68263,-100,4,2,0,60,0,1 +68380,-100,4,2,0,80,0,1 +69201,-100,4,2,0,60,0,1 +69318,-100,4,2,0,80,0,1 +69670,-100,4,2,0,70,0,0 +84670,-100,4,2,0,70,0,0 +97560,-200,4,2,0,70,0,0 +97795,-200,4,2,0,30,0,0 +98966,-100,4,2,0,30,0,0 +99670,-100,4,2,0,70,0,0 +113732,-100,4,2,0,40,0,0 +114670,-100,4,2,0,80,0,1 +115138,-100,4,2,0,60,0,1 +115255,-100,4,2,0,80,0,1 +116076,-100,4,2,0,60,0,1 +116193,-100,4,2,0,80,0,1 +117013,-100,4,2,0,60,0,1 +117130,-100,4,2,0,80,0,1 +117951,-100,4,2,0,60,0,1 +118068,-100,4,2,0,80,0,1 +118888,-100,4,2,0,60,0,1 +119005,-100,4,2,0,80,0,1 +119826,-100,4,2,0,60,0,1 +119943,-100,4,2,0,80,0,1 +120763,-100,4,2,0,60,0,1 +120880,-100,4,2,0,80,0,1 +121701,-100,4,2,0,60,0,1 +121818,-100,4,2,0,80,0,1 +122638,-100,4,2,0,60,0,1 +122755,-100,4,2,0,80,0,1 +123576,-100,4,2,0,60,0,1 +123693,-100,4,2,0,80,0,1 +124513,-100,4,2,0,60,0,1 +124630,-100,4,2,0,80,0,1 +125451,-100,4,2,0,60,0,1 +125568,-100,4,2,0,80,0,1 +126388,-100,4,2,0,60,0,1 +126505,-100,4,2,0,80,0,1 +127326,-100,4,2,0,60,0,1 +127443,-100,4,2,0,80,0,1 +128263,-100,4,2,0,60,0,1 +128380,-100,4,2,0,80,0,1 +129201,-100,4,2,0,60,0,1 +129318,-100,4,2,0,80,0,1 +129670,-200,4,2,0,40,0,0 +144670,-133.333333333333,4,2,0,40,0,0 +159670,-133.333333333333,4,2,0,40,0,0 +163420,-133.333333333333,4,2,0,45,0,0 +163888,-125,4,2,0,50,0,0 +164357,-117.647058823529,4,2,0,55,0,0 +164826,-111.111111111111,4,2,0,60,0,0 +165295,-105.263157894737,4,2,0,65,0,0 +165763,-100,4,2,0,70,0,0 +166232,-100,4,2,0,40,0,0 +167170,-100,4,2,0,80,0,1 +167638,-100,4,2,0,60,0,1 +167755,-100,4,2,0,80,0,1 +168576,-100,4,2,0,60,0,1 +168693,-100,4,2,0,80,0,1 +169513,-100,4,2,0,60,0,1 +169630,-100,4,2,0,80,0,1 +170451,-100,4,2,0,60,0,1 +170568,-100,4,2,0,80,0,1 +171388,-100,4,2,0,60,0,1 +171505,-100,4,2,0,80,0,1 +172326,-100,4,2,0,60,0,1 +172443,-100,4,2,0,80,0,1 +173263,-100,4,2,0,60,0,1 +173380,-100,4,2,0,80,0,1 +174201,-100,4,2,0,60,0,1 +174318,-100,4,2,0,80,0,1 +175138,-100,4,2,0,60,0,1 +175255,-100,4,2,0,80,0,1 +176076,-100,4,2,0,60,0,1 +176193,-100,4,2,0,80,0,1 +177013,-100,4,2,0,60,0,1 +177130,-100,4,2,0,80,0,1 +177951,-100,4,2,0,60,0,1 +178068,-100,4,2,0,80,0,1 +178888,-100,4,2,0,60,0,1 +179005,-100,4,2,0,80,0,1 +179826,-100,4,2,0,60,0,1 +179943,-100,4,2,0,80,0,1 +180763,-100,4,2,0,60,0,1 +180880,-100,4,2,0,80,0,1 +180998,-100,4,2,0,80,0,0 +181466,-100,4,2,0,60,0,0 +181584,-100,4,2,0,80,0,0 +181935,-100,4,2,0,80,0,0 +182170,-100,4,2,0,80,0,1 +182638,-100,4,2,0,60,0,1 +182755,-100,4,2,0,80,0,1 +183576,-100,4,2,0,60,0,1 +183693,-100,4,2,0,80,0,1 +184513,-100,4,2,0,60,0,1 +184630,-100,4,2,0,80,0,1 +185451,-100,4,2,0,60,0,1 +185568,-100,4,2,0,80,0,1 +186388,-100,4,2,0,60,0,1 +186505,-100,4,2,0,80,0,1 +187326,-100,4,2,0,60,0,1 +187443,-100,4,2,0,80,0,1 +188263,-100,4,2,0,60,0,1 +188380,-100,4,2,0,80,0,1 +189201,-100,4,2,0,60,0,1 +189318,-100,4,2,0,80,0,1 +190138,-100,4,2,0,60,0,1 +190255,-100,4,2,0,80,0,1 +191076,-100,4,2,0,60,0,1 +191193,-100,4,2,0,80,0,1 +192013,-100,4,2,0,60,0,1 +192130,-100,4,2,0,80,0,1 +192951,-100,4,2,0,60,0,1 +193068,-100,4,2,0,80,0,1 +193888,-100,4,2,0,60,0,1 +194005,-100,4,2,0,80,0,1 +194826,-100,4,2,0,60,0,1 +194943,-100,4,2,0,80,0,1 +195295,-100,4,2,0,50,0,1 +195529,-100,4,2,0,52,0,1 +195646,-100,4,2,0,54,0,1 +195763,-100,4,2,0,56,0,1 +195880,-100,4,2,0,58,0,1 +195998,-100,4,2,0,60,0,1 +196115,-100,4,2,0,62,0,1 +196232,-100,4,2,0,64,0,1 +196349,-100,4,2,0,68,0,1 +196466,-100,4,2,0,70,0,1 +196584,-100,4,2,0,72,0,1 +196701,-100,4,2,0,74,0,1 +196818,-100,4,2,0,76,0,1 +196935,-100,4,2,0,78,0,1 +197052,-100,4,2,0,80,0,1 +197170,-100,4,2,0,80,0,0 +197873,-100,4,2,0,60,0,0 +197990,-100,4,2,0,80,0,0 +198341,-100,4,2,0,60,0,0 +199045,-100,4,2,0,80,0,0 +199279,-100,4,2,0,60,0,0 +199630,-100,4,2,0,80,0,0 +200216,-100,4,2,0,60,0,0 +200334,-100,4,2,0,80,0,0 +201623,-100,4,2,0,60,0,0 +201740,-100,4,2,0,80,0,0 +202326,-100,4,2,0,60,0,0 +202443,-100,4,2,0,80,0,0 +203029,-100,4,2,0,60,0,0 +203498,-100,4,2,0,80,0,0 +203966,-100,4,2,0,60,0,0 +204201,-100,4,2,0,80,0,0 +205373,-100,4,2,0,60,0,0 +205490,-100,4,2,0,80,0,0 +205841,-100,4,2,0,60,0,0 +206076,-100,4,2,0,60,0,0 +206545,-100,4,2,0,80,0,0 +206779,-100,4,2,0,60,0,0 +207130,-100,4,2,0,80,0,0 +207716,-100,4,2,0,60,0,0 +207951,-100,4,2,0,80,0,0 +209123,-100,4,2,0,60,0,0 +209240,-100,4,2,0,80,0,0 +209826,-100,4,2,0,60,0,0 +209943,-100,4,2,0,80,0,0 +210529,-100,4,2,0,60,0,0 +210880,-100,4,2,0,80,0,0 +211232,-100,4,2,0,60,0,0 +211701,-100,4,2,0,70,0,0 +212170,-100,4,2,0,80,0,0 +212873,-100,4,2,0,60,0,0 +212990,-100,4,2,0,80,0,0 +213341,-100,4,2,0,60,0,0 +213576,-100,4,2,0,60,0,0 +214045,-100,4,2,0,80,0,0 +214279,-100,4,2,0,60,0,0 +214630,-100,4,2,0,80,0,0 +215216,-100,4,2,0,60,0,0 +215451,-100,4,2,0,80,0,0 +216623,-100,4,2,0,60,0,0 +216740,-100,4,2,0,80,0,0 +217326,-100,4,2,0,60,0,0 +217443,-100,4,2,0,80,0,0 +218029,-100,4,2,0,60,0,0 +218498,-100,4,2,0,80,0,0 +218732,-100,4,2,0,50,0,0 +219670,-100,4,2,0,70,0,0 +220138,-100,4,2,0,65,0,0 +220373,-100,4,2,0,45,0,0 +220490,-100,4,2,0,65,0,0 +220607,-100,4,2,0,60,0,0 +220841,-100,4,2,0,35,0,0 +221076,-100,4,2,0,35,0,0 +221545,-100,4,2,0,50,0,0 +221779,-100,4,2,0,30,0,0 +222013,-111.111111111111,4,2,0,25,0,0 +222130,-111.111111111111,4,2,0,40,0,0 +222482,-125,4,2,0,40,0,0 +222716,-125,4,2,0,20,0,0 +222951,-100,4,2,0,15,0,0 +223420,-100,4,2,0,30,0,0 +224357,-100,4,2,0,25,0,0 +225295,-100,4,2,0,20,0,0 +226232,-100,4,2,0,15,0,0 +226701,-100,4,2,0,10,0,0 +227170,-100,4,2,0,5,0,0 + +[Colours] +Combo1 : 17,254,176 +Combo2 : 173,255,95 +Combo3 : 255,88,100 +Combo4 : 255,94,55 + +[HitObjects] +320,256,2170,6,0,P|256:284|192:256,1,144,4|0,0:0|0:0,0:0:0:0: +144,184,2873,1,0,0:0:0:0: +108,260,3107,2,0,P|112:296|100:336,1,72 +28,288,3576,2,0,P|24:252|36:212,1,72,0|0,0:0|0:0,0:0:0:0: +76,140,4045,6,0,L|220:136,1,144,4|0,0:0|0:0,0:0:0:0: +292,88,4748,1,0,0:0:0:0: +292,88,4982,2,0,P|304:120|300:168,1,72 +388,168,5451,2,0,P|396:133|416:103,1,72,0|0,0:0|0:0,0:0:0:0: +472,172,5920,6,0,B|470:200|457:222|457:222|488:256|476:308,1,144,4|0,0:0|0:0,0:0:0:0: +396,280,6623,1,0,0:0:0:0: +324,328,6857,2,0,P|288:332|252:324,1,72 +180,280,7326,2,0,L|108:284,1,72,0|0,0:0|0:0,0:0:0:0: +256,192,7795,12,0,9670,0:0:0:0: +428,212,10138,1,0,0:0:0:0: +292,320,10607,1,0,0:0:0:0: +184,184,11076,2,0,L|112:180,1,72,0|0,0:0|0:0,0:0:0:0: +24,172,11545,5,6,0:0:0:0: +160,280,12013,1,0,0:0:0:0: +268,144,12482,1,0,0:0:0:0: +132,36,12951,2,0,L|204:32,1,72,0|0,0:0|0:0,0:0:0:0: +284,60,13420,6,0,P|340:100|344:180,2,144,6|0|0,0:0|0:0|0:0,0:0:0:0: +268,144,14591,1,0,0:0:0:0: +284,228,14826,2,0,P|316:248|364:252,1,72,0|0,0:0|0:0,0:0:0:0: +436,248,15295,6,0,P|372:272|344:340,1,144,6|2,0:0|0:0,0:0:0:0: +168,338,16232,2,0,P|141:273|76:248,1,144,2|2,0:0|0:0,0:0:0:0: +4,296,16935,1,0,0:0:0:0: +80,336,17170,5,6,0:0:0:0: +44,168,17638,1,0,0:0:0:0: +212,128,18107,1,0,0:0:0:0: +248,296,18576,2,0,P|284:288|320:292,1,72,0|0,0:0|0:0,0:0:0:0: +400,324,19045,5,6,0:0:0:0: +280,200,19513,1,0,0:0:0:0: +368,52,19982,1,0,0:0:0:0: +488,176,20451,2,0,P|452:168|416:172,1,72,0|0,0:0|0:0,0:0:0:0: +336,200,20920,6,0,P|284:216|200:192,1,144,6|0,0:0|0:0,0:0:0:0: +200,192,21857,2,0,L|204:264,1,72,0|0,0:3|0:0,0:0:0:0: +117,244,22326,2,0,L|120:172,1,72,0|0,0:0|0:0,0:0:0:0: +40,152,22795,6,0,L|28:296,2,144,6|0|0,0:0|0:0|0:0,0:0:0:0: +152,24,24201,1,0,0:0:0:0: +220,76,24435,1,0,3:0:0:0: +304,56,24670,6,0,P|288:120|296:196,1,144,4|2,0:3|0:3,0:0:0:0: +344,268,25373,1,0,0:0:0:0: +416,316,25607,2,0,P|452:312|508:316,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +244,344,26545,6,0,P|176:356|108:328,1,144,4|2,0:3|0:3,0:0:0:0: +60,256,27248,1,0,0:0:0:0: +36,172,27482,2,0,L|40:100,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +188,252,28420,6,0,P|192:184|196:100,1,144,4|2,0:3|0:3,0:0:0:0: +140,40,29123,1,0,0:0:0:0: +140,40,29357,2,0,B|172:16|220:24|220:24|288:36,1,144,0|2,0:0|0:3,0:0:0:0: +364,52,30060,1,0,0:0:0:0: +308,116,30295,6,0,B|300:168|300:168|328:256,1,144,4|2,0:3|0:3,0:0:0:0: +340,340,30998,1,0,0:0:0:0: +260,308,31232,2,0,L|188:304,1,72,0|2,0:0|0:3,0:0:0:0: +100,296,31701,1,2,0:3:0:0: +136,374,31935,1,0,0:0:0:0: +152,224,32170,6,0,P|160:152|132:88,1,144,4|2,0:3|0:3,0:0:0:0: +56,48,32873,1,0,0:0:0:0: +60,136,33107,2,0,L|56:208,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +224,76,34045,6,0,P|289:104|360:96,1,144,4|2,0:3|0:3,0:0:0:0: +432,48,34748,1,0,0:0:0:0: +440,132,34982,2,0,B|432:156|432:156|436:204,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +448,304,35920,6,0,B|412:315|380:292|380:292|348:269|312:280,1,144,4|2,0:3|0:3,0:0:0:0: +332,364,36623,1,0,0:0:0:0: +247,339,36857,2,0,P|230:308|225:273,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +312,280,37560,6,0,L|316:172,1,108 +134,35,38966,5,0,0:0:0:0: +72,96,39201,2,0,P|119:119|171:111,1,108,0|0,0:0|0:0,0:0:0:0: +192,100,39670,6,0,L|200:172,1,72,4|2,0:0|0:0,0:0:0:0: +147,240,40138,2,0,P|133:272|132:308,1,72,0|2,1:0|0:0,0:0:0:0: +216,292,40607,2,0,B|260:308|260:308|356:292,1,144,4|0,2:3|1:0,1:0:0:0: +356,292,41310,1,2,0:0:0:0: +436,327,41545,6,0,P|441:292|435:257,1,72,4|2,0:3|0:0,0:0:0:0: +364,204,42013,2,0,P|336:144|352:68,1,144,0|4,1:0|2:3,1:0:0:0: +404,0,42716,1,2,0:0:0:0: +440,80,42951,2,0,B|464:84|464:84|512:80,1,72,0|2,1:0|0:0,0:0:0:0: +351,71,43420,6,0,B|296:68|296:68|268:76|268:76|196:72,1,144,4|0,2:3|1:0,1:0:0:0: +120,68,44123,1,2,0:0:0:0: +160,144,44357,2,0,P|172:180|168:232,1,72,4|2,0:3|0:0,0:0:0:0: +76,264,44826,2,0,P|76:228|88:194,1,72,0|2,1:0|0:0,0:0:0:0: +160,144,45295,5,4,0:3:0:0: +244,164,45529,1,2,0:0:0:0: +268,248,45763,2,0,L|344:252,1,72,0|2,1:0|0:0,0:0:0:0: +408,156,46232,2,0,L|336:159,1,72,4|2,0:3|0:0,0:0:0:0: +212,72,46701,2,0,L|288:76,1,72,0|2,1:0|0:0,0:0:0:0: +400,72,47170,6,0,P|464:96|488:172,1,144,4|0,2:0|1:0,1:0:0:0: +476,248,47873,1,2,0:0:0:0: +436,324,48107,2,0,L|284:320,1,144,4|0,2:3|1:0,1:0:0:0: +204,316,48810,1,2,0:0:0:0: +127,355,49045,6,0,P|120:321|124:285,1,72,4|2,0:3|0:0,0:0:0:0: +192,232,49513,2,0,L|335:228,1,144,0|4,1:0|2:3,1:0:0:0: +412,188,50216,1,2,0:0:0:0: +444,108,50451,2,0,P|452:72|448:36,1,72,0|2,1:0|0:0,0:0:0:0: +368,68,50920,6,0,B|332:79|300:56|300:56|268:33|232:44,1,144,4|0,2:3|1:0,1:0:0:0: +152,76,51623,1,2,0:0:0:0: +76,116,51857,2,0,L|80:268,1,144,4|0,2:3|1:0,1:0:0:0: +80,260,52560,1,2,0:0:0:0: +8,308,52795,6,0,P|34:334|69:346,1,72,4|2,0:3|0:0,0:0:0:0: +148,312,53263,2,0,P|163:278|162:241,1,72,0|2,1:0|0:0,0:0:0:0: +156,156,53732,5,0,3:0:0:0: +156,156,53966,1,2,0:0:0:0: +236,196,54201,2,0,L|312:192,1,72,8|0,0:3|0:0,0:0:0:0: +368,256,54670,6,0,P|392:216|352:116,1,144,4|2,0:0|1:2,0:0:0:0: +288,92,55373,1,0,0:0:0:0: +360,40,55607,2,0,L|432:36,1,72,4|0,0:3|3:0,0:0:0:0: +288,92,56076,2,0,L|216:88,1,72,2|0,1:2|0:0,0:0:0:0: +132,72,56545,6,0,P|172:88|200:184,1,144,4|2,0:3|1:2,0:0:0:0: +143,241,57248,1,0,0:0:0:0: +65,202,57482,2,0,P|87:174|119:157,1,72,4|0,0:3|3:0,0:0:0:0: +132,324,57951,2,0,P|98:312|72:288,1,72,2|0,1:2|0:0,0:0:0:0: +143,241,58420,6,0,L|288:240,1,144,4|2,0:3|1:2,0:0:0:0: +372,240,59123,1,0,0:0:0:0: +330,314,59357,2,0,P|318:350|322:390,1,72,4|0,0:3|3:0,0:0:0:0: +452,264,59826,2,0,P|453:228|442:194,1,72,2|0,1:2|0:0,0:0:0:0: +384,128,60295,6,0,B|336:144|336:144|244:128,1,144,4|2,0:3|1:2,0:0:0:0: +164,160,60998,2,0,P|160:116|168:88,1,72,0|4,0:0|0:3,0:0:0:0: +244,128,61466,2,0,P|248:172|240:200,1,72,0|2,3:0|1:2,0:0:0:0: +168,248,61935,1,0,0:0:0:0: +120,320,62170,6,0,P|196:328|252:272,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: +80,244,63341,1,0,3:0:0:0: +100,160,63576,2,0,L|24:156,1,72,2|0,1:2|0:0,0:0:0:0: +180,128,64045,6,0,P|249:138|304:94,1,144,4|2,0:3|1:2,0:0:0:0: +226,57,64748,1,0,0:0:0:0: +304,94,64982,2,0,L|300:166,1,72,4|0,0:3|3:0,0:0:0:0: +377,203,65451,2,0,L|388:132,1,72,2|0,1:2|0:0,0:0:0:0: +468,180,65920,6,0,L|432:328,1,144,4|2,0:3|1:2,0:0:0:0: +276,252,66857,2,0,P|208:248|140:280,1,144,4|2,0:3|1:2,0:0:0:0: +84,344,67560,1,0,0:0:0:0: +56,260,67795,6,0,L|52:188,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +168,128,68732,2,0,L|172:56,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +244,168,69435,1,0,0:0:0:0: +332,164,69670,1,4,0:3:0:0: +208,328,84670,6,0,P|224:264|216:188,1,144,4|2,0:3|0:3,0:0:0:0: +168,116,85373,1,0,0:0:0:0: +96,68,85607,2,0,P|60:72|4:68,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +268,40,86545,6,0,P|336:28|404:56,1,144,4|2,0:3|0:3,0:0:0:0: +452,128,87248,1,0,0:0:0:0: +476,212,87482,2,0,L|472:284,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +324,132,88420,6,0,P|320:200|316:284,1,144,4|2,0:3|0:3,0:0:0:0: +372,344,89123,1,0,0:0:0:0: +372,344,89357,2,0,B|340:368|292:360|292:360|224:348,1,144,0|2,0:0|0:3,0:0:0:0: +148,332,90060,1,0,0:0:0:0: +204,268,90295,6,0,B|212:216|212:216|184:128,1,144,4|2,0:3|0:3,0:0:0:0: +172,44,90998,1,0,0:0:0:0: +252,76,91232,2,0,L|324:80,1,72,0|2,0:0|0:3,0:0:0:0: +412,88,91701,1,2,0:3:0:0: +377,9,91935,1,0,0:0:0:0: +360,160,92170,6,0,P|352:232|380:296,1,144,4|2,0:3|0:3,0:0:0:0: +456,336,92873,1,0,0:0:0:0: +452,248,93107,2,0,L|456:176,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +288,308,94045,6,0,P|223:280|152:288,1,144,4|2,0:3|0:3,0:0:0:0: +80,336,94748,1,0,0:0:0:0: +72,252,94982,2,0,B|80:228|80:228|76:180,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +64,80,95920,6,0,B|100:69|132:92|132:92|164:115|200:104,1,144,4|2,0:3|0:3,0:0:0:0: +180,20,96623,1,0,0:0:0:0: +265,45,96857,2,0,P|282:76|287:111,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: +200,104,97560,1,0,0:0:0:0: +200,104,97677,1,0,0:0:0:0: +200,104,97795,6,0,B|196:142|217:166|217:166|176:180|160:220,1,144,4|0,0:3|0:0,0:0:0:0: +240,248,98966,5,0,0:0:0:0: +202,325,99201,2,0,P|254:333|301:309,1,108,0|0,0:0|0:0,0:0:0:0: +315,292,99670,6,0,L|323:220,1,72,4|2,0:0|0:0,0:0:0:0: +365,144,100138,2,0,P|379:112|380:76,1,72,0|2,1:0|0:0,0:0:0:0: +296,92,100607,2,0,B|252:76|252:76|156:92,1,144,4|0,2:3|1:0,1:0:0:0: +156,92,101310,1,2,0:0:0:0: +76,57,101545,6,0,P|71:92|77:127,1,72,4|2,0:3|0:0,0:0:0:0: +148,180,102013,2,0,P|176:240|160:316,1,144,0|4,1:0|2:3,1:0:0:0: +108,384,102716,1,2,0:0:0:0: +72,304,102951,2,0,B|48:300|48:300|0:304,1,72,0|2,1:0|0:0,0:0:0:0: +161,313,103420,6,0,B|216:316|216:316|244:308|244:308|316:312,1,144,4|0,2:3|1:0,1:0:0:0: +392,316,104123,1,2,0:0:0:0: +352,240,104357,2,0,P|340:204|344:152,1,72,4|2,0:3|0:0,0:0:0:0: +436,120,104826,2,0,P|436:156|424:190,1,72,0|2,1:0|0:0,0:0:0:0: +352,240,105295,5,4,0:3:0:0: +268,220,105529,1,2,0:0:0:0: +244,136,105763,2,0,L|168:132,1,72,0|2,1:0|0:0,0:0:0:0: +104,228,106232,2,0,L|176:225,1,72,4|2,0:3|0:0,0:0:0:0: +300,312,106701,2,0,L|224:308,1,72,0|2,1:0|0:0,0:0:0:0: +112,312,107170,6,0,P|48:288|24:212,1,144,4|0,2:0|1:0,1:0:0:0: +36,136,107873,1,2,0:0:0:0: +76,60,108107,2,0,L|228:64,1,144,4|0,2:3|1:0,1:0:0:0: +308,68,108810,1,2,0:0:0:0: +385,29,109045,6,0,P|392:63|388:99,1,72,4|2,0:3|0:0,0:0:0:0: +320,152,109513,2,0,L|177:156,1,144,0|4,1:0|2:3,1:0:0:0: +100,196,110216,1,2,0:0:0:0: +68,276,110451,2,0,P|60:312|64:348,1,72,0|2,1:0|0:0,0:0:0:0: +144,316,110920,6,0,B|180:305|212:328|212:328|244:351|280:340,1,144,4|0,2:3|1:0,1:0:0:0: +360,308,111623,1,2,0:0:0:0: +436,268,111857,2,0,L|432:116,1,144,4|0,2:3|1:0,1:0:0:0: +432,124,112560,1,2,0:0:0:0: +504,76,112795,6,0,P|478:50|443:38,1,72,4|2,0:3|0:0,0:0:0:0: +364,72,113263,2,0,P|349:106|350:143,1,72,0|2,1:0|0:0,0:0:0:0: +356,228,113732,5,0,3:0:0:0: +356,228,113966,1,2,0:0:0:0: +276,188,114201,2,0,L|200:192,1,72,8|0,0:3|0:0,0:0:0:0: +144,128,114670,6,0,P|120:168|160:268,1,144,4|2,0:0|1:2,0:0:0:0: +224,292,115373,1,0,0:0:0:0: +152,344,115607,2,0,L|80:348,1,72,4|0,0:3|3:0,0:0:0:0: +224,292,116076,2,0,L|296:296,1,72,2|0,1:2|0:0,0:0:0:0: +380,312,116545,6,0,P|340:296|312:200,1,144,4|2,0:3|1:2,0:0:0:0: +369,143,117248,1,0,0:0:0:0: +447,182,117482,2,0,P|425:210|393:227,1,72,4|0,0:3|3:0,0:0:0:0: +380,60,117951,2,0,P|414:72|440:96,1,72,2|0,1:2|0:0,0:0:0:0: +369,143,118420,6,0,L|224:144,1,144,4|2,0:3|1:2,0:0:0:0: +140,144,119123,1,0,0:0:0:0: +182,70,119357,2,0,P|194:34|190:-6,1,72,4|0,0:3|3:0,0:0:0:0: +60,120,119826,2,0,P|59:156|70:190,1,72,2|0,1:2|0:0,0:0:0:0: +128,256,120295,6,0,B|176:240|176:240|268:256,1,144,4|2,0:3|1:2,0:0:0:0: +348,224,120998,2,0,P|352:268|344:296,1,72,0|4,0:0|0:3,0:0:0:0: +268,256,121466,2,0,P|264:212|272:184,1,72,0|2,3:0|1:2,0:0:0:0: +344,136,121935,1,0,0:0:0:0: +392,64,122170,6,0,P|316:56|260:112,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: +432,140,123341,1,0,3:0:0:0: +412,224,123576,2,0,L|488:228,1,72,2|0,1:2|0:0,0:0:0:0: +332,256,124045,6,0,P|263:246|208:290,1,144,4|2,0:3|1:2,0:0:0:0: +286,327,124748,1,0,0:0:0:0: +208,290,124982,2,0,L|212:218,1,72,4|0,0:3|3:0,0:0:0:0: +135,181,125451,2,0,L|124:252,1,72,2|0,1:2|0:0,0:0:0:0: +44,204,125920,6,0,L|80:56,1,144,4|2,0:3|1:2,0:0:0:0: +236,132,126857,2,0,P|304:136|372:104,1,144,4|2,0:3|1:2,0:0:0:0: +428,40,127560,1,0,0:0:0:0: +456,124,127795,6,0,L|460:196,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +344,256,128732,2,0,L|340:328,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +268,216,129435,1,0,0:0:0:0: +180,220,129670,5,4,2:0:0:0: +256,40,130373,1,2,0:0:0:0: +64,68,131076,1,2,0:0:0:0: +92,136,131310,1,0,0:0:0:0: +64,204,131545,6,0,L|60:288,1,72 +31,343,132248,2,0,P|86:345|127:309,1,108 +332,220,133420,5,2,0:0:0:0: +256,40,134123,1,2,0:0:0:0: +448,68,134826,1,2,0:0:0:0: +420,136,135060,1,0,0:0:0:0: +448,204,135295,6,0,L|452:288,1,72,2|0,0:0|0:0,0:0:0:0: +480,343,135998,2,0,P|426:345|385:309,1,108 +256,192,137170,5,2,0:0:0:0: +156,360,137873,1,2,0:0:0:0: +356,360,138576,2,0,L|352:308,1,36,2|0,0:0|0:0,0:0:0:0: +304,268,139045,6,0,P|336:253|372:252,1,72 +448,260,139748,2,0,L|444:152,1,108 +256,192,140920,5,2,0:0:0:0: +356,24,141623,1,2,0:0:0:0: +156,24,142326,2,0,L|160:72,1,36,2|0,0:0|0:0,0:0:0:0: +208,116,142795,6,0,P|176:131|140:132,1,72,2|0,0:0|0:0,0:0:0:0: +64,124,143498,2,0,L|68:232,1,108 +68,232,144670,5,4,0:3:0:0: +216,320,145138,1,4,0:3:0:0: +304,172,145607,1,4,0:3:0:0: +156,84,146075,1,4,0:3:0:0: +296,320,146545,5,4,0:3:0:0: +208,172,147013,1,4,0:3:0:0: +356,84,147482,1,4,0:3:0:0: +444,232,147950,1,4,0:3:0:0: +296,320,148420,6,0,P|252:328|192:296,2,108.000004119873,4|4|4,0:3|0:3|0:3,0:0:0:0: +260,248,149591,1,0,0:0:0:0: +320,196,149826,2,0,L|316:140,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: +120,236,159670,6,0,L|176:232,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: +160,152,160138,2,0,L|104:156,1,54.0000020599366,2|0,0:0|0:0,0:0:0:0: +240,180,160607,2,0,P|292:188|344:172,1,108.000004119873,4|2,0:3|0:0,3:0:0:0: +408,120,161310,1,0,3:0:0:0: +424,200,161545,6,0,L|420:256,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: +376,320,162013,2,0,P|396:328|480:304,2,108.000004119873,2|6|2,2:0|0:3|2:0,3:0:0:0: +312,268,163185,1,0,0:0:0:0: +296,348,163420,6,0,L|240:344,1,54.0000020599366,4|0,3:0|3:0,0:0:0:0: +160,320,163888,2,0,L|100:316,1,57.6,4|0,3:0|3:0,0:0:0:0: +64,232,164357,6,0,L|128:228,1,61.2000011672974,4|0,3:0|3:0,0:0:0:0: +204,200,164825,2,0,L|268:196,1,61.2000011672974,4|0,3:0|3:0,0:0:0:0: +232,108,165295,6,0,L|164:104,1,68.399998173523,4|0,3:0|3:0,0:0:0:0: +80,84,165763,2,0,L|4:80,1,72,4|0,3:0|3:0,0:0:0:0: +324,120,167170,6,0,P|388:128|456:92,1,144,4|2,0:0|1:2,0:0:0:0: +496,168,167873,1,0,0:0:0:0: +496,168,168107,2,0,P|484:204|488:256,1,72,4|0,0:3|3:0,0:0:0:0: +408,296,168576,2,0,P|398:261|378:231,1,72,2|0,1:2|0:0,0:0:0:0: +296,200,169045,6,0,B|228:228|156:204,1,144,4|2,0:3|1:2,0:0:0:0: +84,156,169748,1,0,0:0:0:0: +80,244,169982,2,0,L|76:316,1,72,4|0,0:3|3:0,0:0:0:0: +170,274,170451,2,0,L|156:204,1,72,2|0,1:2|0:0,0:0:0:0: +216,140,170920,6,0,L|284:276,1,144,4|2,0:3|1:2,0:0:0:0: +320,344,171623,1,0,0:0:0:0: +372,276,171857,2,0,P|366:240|349:207,1,72,4|0,0:3|3:0,0:0:0:0: +312,132,172326,2,0,L|276:60,1,72,2|0,1:2|0:0,0:0:0:0: +208,20,172795,6,0,P|272:36|348:12,1,144,4|2,0:3|1:2,0:0:0:0: +424,48,173498,2,0,L|412:132,1,72,0|4,0:0|0:3,0:0:0:0: +484,168,173966,2,0,L|472:252,1,72,0|2,3:0|1:2,0:0:0:0: +400,280,174435,1,0,0:0:0:0: +346,348,174670,6,0,P|414:363|472:324,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: +312,268,175841,1,0,3:0:0:0: +256,336,176076,2,0,L|184:332,1,72,2|0,1:2|0:0,0:0:0:0: +80,244,176545,6,0,B|140:248|140:248|164:244|164:244|223:247,1,144,4|2,0:3|1:2,0:0:0:0: +312,268,177248,1,0,0:0:0:0: +224,247,177482,2,0,P|240:215|272:187,1,72,4|0,0:3|3:0,0:0:0:0: +204,131,177951,2,0,P|233:111|275:103,1,72,2|0,1:2|0:0,0:0:0:0: +240,23,178420,6,0,B|280:15|316:35|316:35|376:71,1,144,4|2,0:3|1:2,0:0:0:0: +399,236,179357,2,0,B|359:244|323:224|323:224|263:188,1,144,4|2,0:3|1:2,0:0:0:0: +204,132,180060,1,0,0:0:0:0: +184,216,180295,6,0,L|188:288,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +120,156,180998,1,0,0:0:0:0: +56,96,181232,2,0,L|60:24,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: +36,180,181935,1,0,0:0:0:0: +100,240,182170,6,0,P|144:300|116:380,2,144,4|2|4,0:0|1:2|0:3,0:0:0:0: +60,316,183341,1,0,0:0:0:0: +220,352,183576,2,0,L|308:348,1,72,2|0,1:2|0:0,0:0:0:0: +396,264,184045,6,0,B|336:268|336:268|312:264|312:264|253:267,1,144,4|2,0:3|1:2,0:0:0:0: +253,267,184748,1,0,0:0:0:0: +268,180,184982,2,0,L|339:177,1,72,4|0,0:3|0:0,0:0:0:0: +164,280,185451,2,0,L|92:282,1,72,2|0,1:2|0:0,0:0:0:0: +52,208,185920,6,0,P|8:268|32:344,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: +140,212,187091,1,0,0:0:0:0: +92,284,187326,2,0,P|104:316|100:368,1,72,2|0,1:2|0:0,0:0:0:0: +52,208,187795,6,0,P|48:136|76:72,1,144,4|2,0:3|1:2,0:0:0:0: +160,52,188498,2,0,P|188:28|220:16,1,72,0|4,0:0|0:3,0:0:0:0: +232,100,188966,2,0,P|268:93|301:98,1,72,0|2,0:0|1:2,0:0:0:0: +372,152,189435,1,0,0:0:0:0: +420,224,189670,6,0,P|428:296|400:360,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: +372,152,190841,1,0,0:0:0:0: +392,68,191076,2,0,L|465:64,1,72,2|0,1:2|0:0,0:0:0:0: +304,92,191545,6,0,P|236:104|168:76,1,144,4|2,0:3|1:2,0:0:0:0: +108,12,192248,1,0,0:0:0:0: +168,76,192482,2,0,L|172:152,1,72,4|0,0:3|0:0,0:0:0:0: +80,136,192951,2,0,L|101:204,1,72,2|0,1:2|0:0,0:0:0:0: +12,220,193420,6,0,B|50:279|50:279|80:300|120:292,1,144,4|2,0:3|1:2,0:0:0:0: +284,232,194357,2,0,B|320:221|352:244|352:244|384:267|420:256,1,144,4|2,0:3|1:2,0:0:0:0: +488,200,195060,1,0,0:0:0:0: +507,284,195295,6,0,P|492:315|464:338,1,72,4|0,0:0|0:0,0:0:0:0: +380,356,195763,2,0,L|236:352,1,144,0|4,1:0|0:3,0:0:0:0: +152,328,196466,1,0,3:0:0:0: +64,336,196701,2,0,P|29:325|4:300,1,72,0|0,1:0|0:0,0:0:0:0: +76,252,197170,6,0,P|108:188|96:116,1,144,4|0,0:0|1:0,0:0:0:0: +36,56,197873,1,2,0:0:0:0: +120,32,198107,2,0,L|192:28,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +248,152,199045,6,0,P|280:168|304:196,1,72,4|2,0:3|0:0,0:0:0:0: +336,277,199513,2,0,P|306:296|269:303,1,72,2|0,1:2|0:0,0:0:0:0: +183,290,199982,2,0,P|180:254|193:219,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: +436,252,200920,6,0,P|404:188|416:116,1,144,4|0,0:3|1:0,0:0:0:0: +476,56,201623,1,2,0:0:0:0: +392,32,201857,2,0,L|320:28,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: +264,152,202795,6,0,P|232:168|208:196,1,72,4|2,0:3|0:0,0:0:0:0: +176,277,203263,2,0,P|205:296|242:303,1,72,2|0,1:2|0:0,0:0:0:0: +329,290,203732,2,0,P|331:254|318:219,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: +72,324,204670,6,0,B|60:272|60:272|76:180,1,144,4|0,0:0|1:0,0:0:0:0: +92,96,205373,1,2,0:0:0:0: +8,124,205607,2,0,P|5:88|14:53,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +168,192,206545,6,0,P|200:174|237:173,1,72,4|2,0:3|0:0,0:0:0:0: +320,160,207013,2,0,P|318:196|301:229,1,72,2|0,1:2|0:0,0:0:0:0: +272,307,207482,2,0,P|240:287|221:256,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: +440,324,208420,6,0,B|452:272|452:272|436:180,1,144,4|0,0:3|1:0,0:0:0:0: +420,96,209123,1,2,0:0:0:0: +504,124,209357,2,0,P|507:88|498:53,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: +344,192,210295,6,0,P|311:174|274:173,1,72,4|2,0:3|0:0,0:0:0:0: +190,156,210763,2,0,P|191:192|208:225,1,72,2|0,1:2|0:0,0:0:0:0: +288,256,211232,1,4,0:3:0:0: +132,332,211701,1,0,1:0:0:0: +28,192,212170,6,0,P|16:120|44:56,1,144,4|0,0:0|1:0,0:0:0:0: +120,16,212873,1,2,0:0:0:0: +204,32,213107,2,0,L|304:28,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +192,204,214045,6,0,P|196:240|216:272,1,72,4|2,0:3|0:0,0:0:0:0: +298,241,214513,2,0,P|327:219|345:186,1,72,6|0,1:2|0:0,0:0:0:0: +280,132,214982,2,0,P|246:117|209:118,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: +484,192,215920,6,0,P|496:120|468:56,1,144,4|0,0:3|1:0,0:0:0:0: +392,16,216623,1,2,0:0:0:0: +308,32,216857,2,0,L|208:28,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: +320,204,217795,6,0,P|316:240|296:272,1,72,4|2,0:3|0:0,0:0:0:0: +213,241,218263,2,0,P|184:219|166:186,1,72,2|0,1:2|0:0,0:0:0:0: +232,132,218732,2,0,B|260:112|300:116|300:116|384:128,1,144,4|0,0:3|1:0,0:0:0:0: +348,336,219670,6,0,B|320:356|280:352|280:352|196:340,1,144,4|0,0:0|1:0,0:0:0:0: +124,328,220373,1,2,0:0:0:0: +54,276,220607,2,0,P|41:308|39:345,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: +156,80,221545,6,0,L|251:94,1,72,4|2,0:3|0:0,0:0:0:0: +212,169,222013,2,0,L|148:160,1,64.799998022461,2|0,1:2|0:0,0:0:0:0: +140,240,222482,2,0,L|216:252,2,57.6,4|2|0,0:3|0:0|1:0,0:0:0:0: +256,192,223420,12,0,227170,0:0:0:0: +"; + } +} diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index af482dc250..02a5c8a5fc 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -1,14 +1,22 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Tests.Beatmaps { public class TestWorkingBeatmap : WorkingBeatmap { + public TestWorkingBeatmap(RulesetInfo ruleset) + : this(new TestBeatmap(ruleset)) + { + } + public TestWorkingBeatmap(Beatmap beatmap) : base(beatmap.BeatmapInfo) { @@ -16,9 +24,23 @@ namespace osu.Game.Tests.Beatmaps } private readonly Beatmap beatmap; - protected override Beatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override Track GetTrack() => new TrackVirtual(); + + protected override Track GetTrack() + { + var lastObject = beatmap.HitObjects.LastOrDefault(); + if (lastObject != null) + return new TestTrack(((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime) + 1000); + return new TrackVirtual(); + } + + private class TestTrack : TrackVirtual + { + public TestTrack(double length) + { + Length = length; + } + } } } diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs new file mode 100644 index 0000000000..982a3c5d73 --- /dev/null +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Rulesets; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Screens; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + public abstract class EditorTestCase : ScreenTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; + + private readonly Ruleset ruleset; + + protected EditorTestCase(Ruleset ruleset) + { + this.ruleset = ruleset; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + osuGame.Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); + + LoadComponentAsync(new Editor(), LoadScreen); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 5b32433467..7ca69c14b8 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual private void load(BeatmapManager beatmaps) { var sets = beatmaps.GetAllUsableBeatmapSets(); - var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID < 0 || b.RulesetID == ruleset.LegacyID); + var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID == null || b.RulesetID == ruleset.LegacyID); allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b))); } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 181ed5e0e6..b7c66a37e5 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -1,9 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.IO; using System.Linq; -using System.Text; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; @@ -59,24 +57,13 @@ namespace osu.Game.Tests.Visual } } - protected virtual Beatmap CreateBeatmap() - { - Beatmap beatmap; - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) - using (var reader = new StreamReader(stream)) - beatmap = Game.Beatmaps.Formats.Decoder.GetDecoder(reader).DecodeBeatmap(reader); - - return beatmap; - } + protected virtual Beatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance()); private Player loadPlayerFor(Ruleset r) { - var beatmap = CreateBeatmap(); - - beatmap.BeatmapInfo.Ruleset = r.RulesetInfo; + var beatmap = CreateBeatmap(r); working = new TestWorkingBeatmap(beatmap); working.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; @@ -107,699 +94,5 @@ namespace osu.Game.Tests.Visual AllowLeadIn = false, AllowResults = false, }; - - private const string test_beatmap_data = - @"osu file format v14 - -[General] -AudioLeadIn: 500 -PreviewTime: 53498 -Countdown: 0 -SampleSet: Soft -StackLeniency: 0.7 -Mode: 0 -LetterboxInBreaks: 0 -WidescreenStoryboard: 1 - -[Editor] -DistanceSpacing: 1.2 -BeatDivisor: 4 -GridSize: 4 -TimelineZoom: 1 - -[Metadata] -Title:My Love -TitleUnicode:My Love -Artist:Kuba Oms -ArtistUnicode:Kuba Oms -Creator:W h i t e -Version:Hard -Source:ADHD -Tags:Monthly Beatmapping Contest Electronic folk pop w_h_i_t_e -BeatmapID:397534 -BeatmapSetID:163112 - -[Difficulty] -HPDrainRate:5 -CircleSize:4 -OverallDifficulty:6 -ApproachRate:7 -SliderMultiplier:1.44 -SliderTickRate:2 - -[Events] -//Break Periods -2,69870,83770 -2,152170,158770 -//Storyboard Layer 0 (Background) -//Storyboard Layer 1 (Fail) -//Storyboard Layer 2 (Pass) -//Storyboard Layer 3 (Foreground) -//Storyboard Sound Samples - -[TimingPoints] -2170,468.75,4,2,0,40,1,0 -4045,-100,4,2,0,30,0,0 -4162,-100,4,2,0,40,0,0 -5920,-100,4,2,0,30,0,0 -6037,-100,4,2,0,40,0,0 -7795,-100,4,2,0,30,0,0 -7912,-100,4,2,0,40,0,0 -9670,-100,4,2,0,40,0,0 -9787,-100,4,2,0,50,0,0 -11545,-100,4,2,0,40,0,0 -11662,-100,4,2,0,50,0,0 -13420,-100,4,2,0,40,0,0 -13537,-100,4,2,0,50,0,0 -15295,-100,4,2,0,40,0,0 -15412,-100,4,2,0,50,0,0 -17170,-100,4,2,0,40,0,0 -17287,-100,4,2,0,50,0,0 -19045,-100,4,2,0,40,0,0 -19162,-100,4,2,0,50,0,0 -20920,-100,4,2,0,40,0,0 -21037,-100,4,2,0,50,0,0 -22795,-100,4,2,0,40,0,0 -22912,-100,4,2,0,50,0,0 -24670,-100,4,2,0,70,0,0 -37560,-200,4,2,0,30,0,0 -38263,-200,4,2,0,5,0,0 -38966,-100,4,2,0,30,0,0 -39670,-100,4,2,0,70,0,0 -53732,-100,4,2,0,40,0,0 -54670,-100,4,2,0,80,0,1 -55138,-100,4,2,0,60,0,1 -55255,-100,4,2,0,80,0,1 -56076,-100,4,2,0,60,0,1 -56193,-100,4,2,0,80,0,1 -57013,-100,4,2,0,60,0,1 -57130,-100,4,2,0,80,0,1 -57951,-100,4,2,0,60,0,1 -58068,-100,4,2,0,80,0,1 -58888,-100,4,2,0,60,0,1 -59005,-100,4,2,0,80,0,1 -59826,-100,4,2,0,60,0,1 -59943,-100,4,2,0,80,0,1 -60763,-100,4,2,0,60,0,1 -60880,-100,4,2,0,80,0,1 -61701,-100,4,2,0,60,0,1 -61818,-100,4,2,0,80,0,1 -62638,-100,4,2,0,60,0,1 -62755,-100,4,2,0,80,0,1 -63576,-100,4,2,0,60,0,1 -63693,-100,4,2,0,80,0,1 -64513,-100,4,2,0,60,0,1 -64630,-100,4,2,0,80,0,1 -65451,-100,4,2,0,60,0,1 -65568,-100,4,2,0,80,0,1 -66388,-100,4,2,0,60,0,1 -66505,-100,4,2,0,80,0,1 -67326,-100,4,2,0,60,0,1 -67443,-100,4,2,0,80,0,1 -68263,-100,4,2,0,60,0,1 -68380,-100,4,2,0,80,0,1 -69201,-100,4,2,0,60,0,1 -69318,-100,4,2,0,80,0,1 -69670,-100,4,2,0,70,0,0 -84670,-100,4,2,0,70,0,0 -97560,-200,4,2,0,70,0,0 -97795,-200,4,2,0,30,0,0 -98966,-100,4,2,0,30,0,0 -99670,-100,4,2,0,70,0,0 -113732,-100,4,2,0,40,0,0 -114670,-100,4,2,0,80,0,1 -115138,-100,4,2,0,60,0,1 -115255,-100,4,2,0,80,0,1 -116076,-100,4,2,0,60,0,1 -116193,-100,4,2,0,80,0,1 -117013,-100,4,2,0,60,0,1 -117130,-100,4,2,0,80,0,1 -117951,-100,4,2,0,60,0,1 -118068,-100,4,2,0,80,0,1 -118888,-100,4,2,0,60,0,1 -119005,-100,4,2,0,80,0,1 -119826,-100,4,2,0,60,0,1 -119943,-100,4,2,0,80,0,1 -120763,-100,4,2,0,60,0,1 -120880,-100,4,2,0,80,0,1 -121701,-100,4,2,0,60,0,1 -121818,-100,4,2,0,80,0,1 -122638,-100,4,2,0,60,0,1 -122755,-100,4,2,0,80,0,1 -123576,-100,4,2,0,60,0,1 -123693,-100,4,2,0,80,0,1 -124513,-100,4,2,0,60,0,1 -124630,-100,4,2,0,80,0,1 -125451,-100,4,2,0,60,0,1 -125568,-100,4,2,0,80,0,1 -126388,-100,4,2,0,60,0,1 -126505,-100,4,2,0,80,0,1 -127326,-100,4,2,0,60,0,1 -127443,-100,4,2,0,80,0,1 -128263,-100,4,2,0,60,0,1 -128380,-100,4,2,0,80,0,1 -129201,-100,4,2,0,60,0,1 -129318,-100,4,2,0,80,0,1 -129670,-200,4,2,0,40,0,0 -144670,-133.333333333333,4,2,0,40,0,0 -159670,-133.333333333333,4,2,0,40,0,0 -163420,-133.333333333333,4,2,0,45,0,0 -163888,-125,4,2,0,50,0,0 -164357,-117.647058823529,4,2,0,55,0,0 -164826,-111.111111111111,4,2,0,60,0,0 -165295,-105.263157894737,4,2,0,65,0,0 -165763,-100,4,2,0,70,0,0 -166232,-100,4,2,0,40,0,0 -167170,-100,4,2,0,80,0,1 -167638,-100,4,2,0,60,0,1 -167755,-100,4,2,0,80,0,1 -168576,-100,4,2,0,60,0,1 -168693,-100,4,2,0,80,0,1 -169513,-100,4,2,0,60,0,1 -169630,-100,4,2,0,80,0,1 -170451,-100,4,2,0,60,0,1 -170568,-100,4,2,0,80,0,1 -171388,-100,4,2,0,60,0,1 -171505,-100,4,2,0,80,0,1 -172326,-100,4,2,0,60,0,1 -172443,-100,4,2,0,80,0,1 -173263,-100,4,2,0,60,0,1 -173380,-100,4,2,0,80,0,1 -174201,-100,4,2,0,60,0,1 -174318,-100,4,2,0,80,0,1 -175138,-100,4,2,0,60,0,1 -175255,-100,4,2,0,80,0,1 -176076,-100,4,2,0,60,0,1 -176193,-100,4,2,0,80,0,1 -177013,-100,4,2,0,60,0,1 -177130,-100,4,2,0,80,0,1 -177951,-100,4,2,0,60,0,1 -178068,-100,4,2,0,80,0,1 -178888,-100,4,2,0,60,0,1 -179005,-100,4,2,0,80,0,1 -179826,-100,4,2,0,60,0,1 -179943,-100,4,2,0,80,0,1 -180763,-100,4,2,0,60,0,1 -180880,-100,4,2,0,80,0,1 -180998,-100,4,2,0,80,0,0 -181466,-100,4,2,0,60,0,0 -181584,-100,4,2,0,80,0,0 -181935,-100,4,2,0,80,0,0 -182170,-100,4,2,0,80,0,1 -182638,-100,4,2,0,60,0,1 -182755,-100,4,2,0,80,0,1 -183576,-100,4,2,0,60,0,1 -183693,-100,4,2,0,80,0,1 -184513,-100,4,2,0,60,0,1 -184630,-100,4,2,0,80,0,1 -185451,-100,4,2,0,60,0,1 -185568,-100,4,2,0,80,0,1 -186388,-100,4,2,0,60,0,1 -186505,-100,4,2,0,80,0,1 -187326,-100,4,2,0,60,0,1 -187443,-100,4,2,0,80,0,1 -188263,-100,4,2,0,60,0,1 -188380,-100,4,2,0,80,0,1 -189201,-100,4,2,0,60,0,1 -189318,-100,4,2,0,80,0,1 -190138,-100,4,2,0,60,0,1 -190255,-100,4,2,0,80,0,1 -191076,-100,4,2,0,60,0,1 -191193,-100,4,2,0,80,0,1 -192013,-100,4,2,0,60,0,1 -192130,-100,4,2,0,80,0,1 -192951,-100,4,2,0,60,0,1 -193068,-100,4,2,0,80,0,1 -193888,-100,4,2,0,60,0,1 -194005,-100,4,2,0,80,0,1 -194826,-100,4,2,0,60,0,1 -194943,-100,4,2,0,80,0,1 -195295,-100,4,2,0,50,0,1 -195529,-100,4,2,0,52,0,1 -195646,-100,4,2,0,54,0,1 -195763,-100,4,2,0,56,0,1 -195880,-100,4,2,0,58,0,1 -195998,-100,4,2,0,60,0,1 -196115,-100,4,2,0,62,0,1 -196232,-100,4,2,0,64,0,1 -196349,-100,4,2,0,68,0,1 -196466,-100,4,2,0,70,0,1 -196584,-100,4,2,0,72,0,1 -196701,-100,4,2,0,74,0,1 -196818,-100,4,2,0,76,0,1 -196935,-100,4,2,0,78,0,1 -197052,-100,4,2,0,80,0,1 -197170,-100,4,2,0,80,0,0 -197873,-100,4,2,0,60,0,0 -197990,-100,4,2,0,80,0,0 -198341,-100,4,2,0,60,0,0 -199045,-100,4,2,0,80,0,0 -199279,-100,4,2,0,60,0,0 -199630,-100,4,2,0,80,0,0 -200216,-100,4,2,0,60,0,0 -200334,-100,4,2,0,80,0,0 -201623,-100,4,2,0,60,0,0 -201740,-100,4,2,0,80,0,0 -202326,-100,4,2,0,60,0,0 -202443,-100,4,2,0,80,0,0 -203029,-100,4,2,0,60,0,0 -203498,-100,4,2,0,80,0,0 -203966,-100,4,2,0,60,0,0 -204201,-100,4,2,0,80,0,0 -205373,-100,4,2,0,60,0,0 -205490,-100,4,2,0,80,0,0 -205841,-100,4,2,0,60,0,0 -206076,-100,4,2,0,60,0,0 -206545,-100,4,2,0,80,0,0 -206779,-100,4,2,0,60,0,0 -207130,-100,4,2,0,80,0,0 -207716,-100,4,2,0,60,0,0 -207951,-100,4,2,0,80,0,0 -209123,-100,4,2,0,60,0,0 -209240,-100,4,2,0,80,0,0 -209826,-100,4,2,0,60,0,0 -209943,-100,4,2,0,80,0,0 -210529,-100,4,2,0,60,0,0 -210880,-100,4,2,0,80,0,0 -211232,-100,4,2,0,60,0,0 -211701,-100,4,2,0,70,0,0 -212170,-100,4,2,0,80,0,0 -212873,-100,4,2,0,60,0,0 -212990,-100,4,2,0,80,0,0 -213341,-100,4,2,0,60,0,0 -213576,-100,4,2,0,60,0,0 -214045,-100,4,2,0,80,0,0 -214279,-100,4,2,0,60,0,0 -214630,-100,4,2,0,80,0,0 -215216,-100,4,2,0,60,0,0 -215451,-100,4,2,0,80,0,0 -216623,-100,4,2,0,60,0,0 -216740,-100,4,2,0,80,0,0 -217326,-100,4,2,0,60,0,0 -217443,-100,4,2,0,80,0,0 -218029,-100,4,2,0,60,0,0 -218498,-100,4,2,0,80,0,0 -218732,-100,4,2,0,50,0,0 -219670,-100,4,2,0,70,0,0 -220138,-100,4,2,0,65,0,0 -220373,-100,4,2,0,45,0,0 -220490,-100,4,2,0,65,0,0 -220607,-100,4,2,0,60,0,0 -220841,-100,4,2,0,35,0,0 -221076,-100,4,2,0,35,0,0 -221545,-100,4,2,0,50,0,0 -221779,-100,4,2,0,30,0,0 -222013,-111.111111111111,4,2,0,25,0,0 -222130,-111.111111111111,4,2,0,40,0,0 -222482,-125,4,2,0,40,0,0 -222716,-125,4,2,0,20,0,0 -222951,-100,4,2,0,15,0,0 -223420,-100,4,2,0,30,0,0 -224357,-100,4,2,0,25,0,0 -225295,-100,4,2,0,20,0,0 -226232,-100,4,2,0,15,0,0 -226701,-100,4,2,0,10,0,0 -227170,-100,4,2,0,5,0,0 - - -[Colours] - Combo1 : 17,254,176 -Combo2 : 173,255,95 -Combo3 : 255,88,100 -Combo4 : 255,94,55 - -[HitObjects] -320,256,2170,6,0,P|256:284|192:256,1,144,4|0,0:0|0:0,0:0:0:0: -144,184,2873,1,0,0:0:0:0: -108,260,3107,2,0,P|112:296|100:336,1,72 -28,288,3576,2,0,P|24:252|36:212,1,72,0|0,0:0|0:0,0:0:0:0: -76,140,4045,6,0,L|220:136,1,144,4|0,0:0|0:0,0:0:0:0: -292,88,4748,1,0,0:0:0:0: -292,88,4982,2,0,P|304:120|300:168,1,72 -388,168,5451,2,0,P|396:133|416:103,1,72,0|0,0:0|0:0,0:0:0:0: -472,172,5920,6,0,B|470:200|457:222|457:222|488:256|476:308,1,144,4|0,0:0|0:0,0:0:0:0: -396,280,6623,1,0,0:0:0:0: -324,328,6857,2,0,P|288:332|252:324,1,72 -180,280,7326,2,0,L|108:284,1,72,0|0,0:0|0:0,0:0:0:0: -256,192,7795,12,0,9670,0:0:0:0: -428,212,10138,1,0,0:0:0:0: -292,320,10607,1,0,0:0:0:0: -184,184,11076,2,0,L|112:180,1,72,0|0,0:0|0:0,0:0:0:0: -24,172,11545,5,6,0:0:0:0: -160,280,12013,1,0,0:0:0:0: -268,144,12482,1,0,0:0:0:0: -132,36,12951,2,0,L|204:32,1,72,0|0,0:0|0:0,0:0:0:0: -284,60,13420,6,0,P|340:100|344:180,2,144,6|0|0,0:0|0:0|0:0,0:0:0:0: -268,144,14591,1,0,0:0:0:0: -284,228,14826,2,0,P|316:248|364:252,1,72,0|0,0:0|0:0,0:0:0:0: -436,248,15295,6,0,P|372:272|344:340,1,144,6|2,0:0|0:0,0:0:0:0: -168,338,16232,2,0,P|141:273|76:248,1,144,2|2,0:0|0:0,0:0:0:0: -4,296,16935,1,0,0:0:0:0: -80,336,17170,5,6,0:0:0:0: -44,168,17638,1,0,0:0:0:0: -212,128,18107,1,0,0:0:0:0: -248,296,18576,2,0,P|284:288|320:292,1,72,0|0,0:0|0:0,0:0:0:0: -400,324,19045,5,6,0:0:0:0: -280,200,19513,1,0,0:0:0:0: -368,52,19982,1,0,0:0:0:0: -488,176,20451,2,0,P|452:168|416:172,1,72,0|0,0:0|0:0,0:0:0:0: -336,200,20920,6,0,P|284:216|200:192,1,144,6|0,0:0|0:0,0:0:0:0: -200,192,21857,2,0,L|204:264,1,72,0|0,0:3|0:0,0:0:0:0: -117,244,22326,2,0,L|120:172,1,72,0|0,0:0|0:0,0:0:0:0: -40,152,22795,6,0,L|28:296,2,144,6|0|0,0:0|0:0|0:0,0:0:0:0: -152,24,24201,1,0,0:0:0:0: -220,76,24435,1,0,3:0:0:0: -304,56,24670,6,0,P|288:120|296:196,1,144,4|2,0:3|0:3,0:0:0:0: -344,268,25373,1,0,0:0:0:0: -416,316,25607,2,0,P|452:312|508:316,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -244,344,26545,6,0,P|176:356|108:328,1,144,4|2,0:3|0:3,0:0:0:0: -60,256,27248,1,0,0:0:0:0: -36,172,27482,2,0,L|40:100,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -188,252,28420,6,0,P|192:184|196:100,1,144,4|2,0:3|0:3,0:0:0:0: -140,40,29123,1,0,0:0:0:0: -140,40,29357,2,0,B|172:16|220:24|220:24|288:36,1,144,0|2,0:0|0:3,0:0:0:0: -364,52,30060,1,0,0:0:0:0: -308,116,30295,6,0,B|300:168|300:168|328:256,1,144,4|2,0:3|0:3,0:0:0:0: -340,340,30998,1,0,0:0:0:0: -260,308,31232,2,0,L|188:304,1,72,0|2,0:0|0:3,0:0:0:0: -100,296,31701,1,2,0:3:0:0: -136,374,31935,1,0,0:0:0:0: -152,224,32170,6,0,P|160:152|132:88,1,144,4|2,0:3|0:3,0:0:0:0: -56,48,32873,1,0,0:0:0:0: -60,136,33107,2,0,L|56:208,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -224,76,34045,6,0,P|289:104|360:96,1,144,4|2,0:3|0:3,0:0:0:0: -432,48,34748,1,0,0:0:0:0: -440,132,34982,2,0,B|432:156|432:156|436:204,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -448,304,35920,6,0,B|412:315|380:292|380:292|348:269|312:280,1,144,4|2,0:3|0:3,0:0:0:0: -332,364,36623,1,0,0:0:0:0: -247,339,36857,2,0,P|230:308|225:273,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -312,280,37560,6,0,L|316:172,1,108 -134,35,38966,5,0,0:0:0:0: -72,96,39201,2,0,P|119:119|171:111,1,108,0|0,0:0|0:0,0:0:0:0: -192,100,39670,6,0,L|200:172,1,72,4|2,0:0|0:0,0:0:0:0: -147,240,40138,2,0,P|133:272|132:308,1,72,0|2,1:0|0:0,0:0:0:0: -216,292,40607,2,0,B|260:308|260:308|356:292,1,144,4|0,2:3|1:0,1:0:0:0: -356,292,41310,1,2,0:0:0:0: -436,327,41545,6,0,P|441:292|435:257,1,72,4|2,0:3|0:0,0:0:0:0: -364,204,42013,2,0,P|336:144|352:68,1,144,0|4,1:0|2:3,1:0:0:0: -404,0,42716,1,2,0:0:0:0: -440,80,42951,2,0,B|464:84|464:84|512:80,1,72,0|2,1:0|0:0,0:0:0:0: -351,71,43420,6,0,B|296:68|296:68|268:76|268:76|196:72,1,144,4|0,2:3|1:0,1:0:0:0: -120,68,44123,1,2,0:0:0:0: -160,144,44357,2,0,P|172:180|168:232,1,72,4|2,0:3|0:0,0:0:0:0: -76,264,44826,2,0,P|76:228|88:194,1,72,0|2,1:0|0:0,0:0:0:0: -160,144,45295,5,4,0:3:0:0: -244,164,45529,1,2,0:0:0:0: -268,248,45763,2,0,L|344:252,1,72,0|2,1:0|0:0,0:0:0:0: -408,156,46232,2,0,L|336:159,1,72,4|2,0:3|0:0,0:0:0:0: -212,72,46701,2,0,L|288:76,1,72,0|2,1:0|0:0,0:0:0:0: -400,72,47170,6,0,P|464:96|488:172,1,144,4|0,2:0|1:0,1:0:0:0: -476,248,47873,1,2,0:0:0:0: -436,324,48107,2,0,L|284:320,1,144,4|0,2:3|1:0,1:0:0:0: -204,316,48810,1,2,0:0:0:0: -127,355,49045,6,0,P|120:321|124:285,1,72,4|2,0:3|0:0,0:0:0:0: -192,232,49513,2,0,L|335:228,1,144,0|4,1:0|2:3,1:0:0:0: -412,188,50216,1,2,0:0:0:0: -444,108,50451,2,0,P|452:72|448:36,1,72,0|2,1:0|0:0,0:0:0:0: -368,68,50920,6,0,B|332:79|300:56|300:56|268:33|232:44,1,144,4|0,2:3|1:0,1:0:0:0: -152,76,51623,1,2,0:0:0:0: -76,116,51857,2,0,L|80:268,1,144,4|0,2:3|1:0,1:0:0:0: -80,260,52560,1,2,0:0:0:0: -8,308,52795,6,0,P|34:334|69:346,1,72,4|2,0:3|0:0,0:0:0:0: -148,312,53263,2,0,P|163:278|162:241,1,72,0|2,1:0|0:0,0:0:0:0: -156,156,53732,5,0,3:0:0:0: -156,156,53966,1,2,0:0:0:0: -236,196,54201,2,0,L|312:192,1,72,8|0,0:3|0:0,0:0:0:0: -368,256,54670,6,0,P|392:216|352:116,1,144,4|2,0:0|1:2,0:0:0:0: -288,92,55373,1,0,0:0:0:0: -360,40,55607,2,0,L|432:36,1,72,4|0,0:3|3:0,0:0:0:0: -288,92,56076,2,0,L|216:88,1,72,2|0,1:2|0:0,0:0:0:0: -132,72,56545,6,0,P|172:88|200:184,1,144,4|2,0:3|1:2,0:0:0:0: -143,241,57248,1,0,0:0:0:0: -65,202,57482,2,0,P|87:174|119:157,1,72,4|0,0:3|3:0,0:0:0:0: -132,324,57951,2,0,P|98:312|72:288,1,72,2|0,1:2|0:0,0:0:0:0: -143,241,58420,6,0,L|288:240,1,144,4|2,0:3|1:2,0:0:0:0: -372,240,59123,1,0,0:0:0:0: -330,314,59357,2,0,P|318:350|322:390,1,72,4|0,0:3|3:0,0:0:0:0: -452,264,59826,2,0,P|453:228|442:194,1,72,2|0,1:2|0:0,0:0:0:0: -384,128,60295,6,0,B|336:144|336:144|244:128,1,144,4|2,0:3|1:2,0:0:0:0: -164,160,60998,2,0,P|160:116|168:88,1,72,0|4,0:0|0:3,0:0:0:0: -244,128,61466,2,0,P|248:172|240:200,1,72,0|2,3:0|1:2,0:0:0:0: -168,248,61935,1,0,0:0:0:0: -120,320,62170,6,0,P|196:328|252:272,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: -80,244,63341,1,0,3:0:0:0: -100,160,63576,2,0,L|24:156,1,72,2|0,1:2|0:0,0:0:0:0: -180,128,64045,6,0,P|249:138|304:94,1,144,4|2,0:3|1:2,0:0:0:0: -226,57,64748,1,0,0:0:0:0: -304,94,64982,2,0,L|300:166,1,72,4|0,0:3|3:0,0:0:0:0: -377,203,65451,2,0,L|388:132,1,72,2|0,1:2|0:0,0:0:0:0: -468,180,65920,6,0,L|432:328,1,144,4|2,0:3|1:2,0:0:0:0: -276,252,66857,2,0,P|208:248|140:280,1,144,4|2,0:3|1:2,0:0:0:0: -84,344,67560,1,0,0:0:0:0: -56,260,67795,6,0,L|52:188,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -168,128,68732,2,0,L|172:56,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -244,168,69435,1,0,0:0:0:0: -332,164,69670,1,4,0:3:0:0: -208,328,84670,6,0,P|224:264|216:188,1,144,4|2,0:3|0:3,0:0:0:0: -168,116,85373,1,0,0:0:0:0: -96,68,85607,2,0,P|60:72|4:68,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -268,40,86545,6,0,P|336:28|404:56,1,144,4|2,0:3|0:3,0:0:0:0: -452,128,87248,1,0,0:0:0:0: -476,212,87482,2,0,L|472:284,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -324,132,88420,6,0,P|320:200|316:284,1,144,4|2,0:3|0:3,0:0:0:0: -372,344,89123,1,0,0:0:0:0: -372,344,89357,2,0,B|340:368|292:360|292:360|224:348,1,144,0|2,0:0|0:3,0:0:0:0: -148,332,90060,1,0,0:0:0:0: -204,268,90295,6,0,B|212:216|212:216|184:128,1,144,4|2,0:3|0:3,0:0:0:0: -172,44,90998,1,0,0:0:0:0: -252,76,91232,2,0,L|324:80,1,72,0|2,0:0|0:3,0:0:0:0: -412,88,91701,1,2,0:3:0:0: -377,9,91935,1,0,0:0:0:0: -360,160,92170,6,0,P|352:232|380:296,1,144,4|2,0:3|0:3,0:0:0:0: -456,336,92873,1,0,0:0:0:0: -452,248,93107,2,0,L|456:176,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -288,308,94045,6,0,P|223:280|152:288,1,144,4|2,0:3|0:3,0:0:0:0: -80,336,94748,1,0,0:0:0:0: -72,252,94982,2,0,B|80:228|80:228|76:180,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -64,80,95920,6,0,B|100:69|132:92|132:92|164:115|200:104,1,144,4|2,0:3|0:3,0:0:0:0: -180,20,96623,1,0,0:0:0:0: -265,45,96857,2,0,P|282:76|287:111,2,72,0|0|2,0:0|0:0|0:3,0:0:0:0: -200,104,97560,1,0,0:0:0:0: -200,104,97677,1,0,0:0:0:0: -200,104,97795,6,0,B|196:142|217:166|217:166|176:180|160:220,1,144,4|0,0:3|0:0,0:0:0:0: -240,248,98966,5,0,0:0:0:0: -202,325,99201,2,0,P|254:333|301:309,1,108,0|0,0:0|0:0,0:0:0:0: -315,292,99670,6,0,L|323:220,1,72,4|2,0:0|0:0,0:0:0:0: -365,144,100138,2,0,P|379:112|380:76,1,72,0|2,1:0|0:0,0:0:0:0: -296,92,100607,2,0,B|252:76|252:76|156:92,1,144,4|0,2:3|1:0,1:0:0:0: -156,92,101310,1,2,0:0:0:0: -76,57,101545,6,0,P|71:92|77:127,1,72,4|2,0:3|0:0,0:0:0:0: -148,180,102013,2,0,P|176:240|160:316,1,144,0|4,1:0|2:3,1:0:0:0: -108,384,102716,1,2,0:0:0:0: -72,304,102951,2,0,B|48:300|48:300|0:304,1,72,0|2,1:0|0:0,0:0:0:0: -161,313,103420,6,0,B|216:316|216:316|244:308|244:308|316:312,1,144,4|0,2:3|1:0,1:0:0:0: -392,316,104123,1,2,0:0:0:0: -352,240,104357,2,0,P|340:204|344:152,1,72,4|2,0:3|0:0,0:0:0:0: -436,120,104826,2,0,P|436:156|424:190,1,72,0|2,1:0|0:0,0:0:0:0: -352,240,105295,5,4,0:3:0:0: -268,220,105529,1,2,0:0:0:0: -244,136,105763,2,0,L|168:132,1,72,0|2,1:0|0:0,0:0:0:0: -104,228,106232,2,0,L|176:225,1,72,4|2,0:3|0:0,0:0:0:0: -300,312,106701,2,0,L|224:308,1,72,0|2,1:0|0:0,0:0:0:0: -112,312,107170,6,0,P|48:288|24:212,1,144,4|0,2:0|1:0,1:0:0:0: -36,136,107873,1,2,0:0:0:0: -76,60,108107,2,0,L|228:64,1,144,4|0,2:3|1:0,1:0:0:0: -308,68,108810,1,2,0:0:0:0: -385,29,109045,6,0,P|392:63|388:99,1,72,4|2,0:3|0:0,0:0:0:0: -320,152,109513,2,0,L|177:156,1,144,0|4,1:0|2:3,1:0:0:0: -100,196,110216,1,2,0:0:0:0: -68,276,110451,2,0,P|60:312|64:348,1,72,0|2,1:0|0:0,0:0:0:0: -144,316,110920,6,0,B|180:305|212:328|212:328|244:351|280:340,1,144,4|0,2:3|1:0,1:0:0:0: -360,308,111623,1,2,0:0:0:0: -436,268,111857,2,0,L|432:116,1,144,4|0,2:3|1:0,1:0:0:0: -432,124,112560,1,2,0:0:0:0: -504,76,112795,6,0,P|478:50|443:38,1,72,4|2,0:3|0:0,0:0:0:0: -364,72,113263,2,0,P|349:106|350:143,1,72,0|2,1:0|0:0,0:0:0:0: -356,228,113732,5,0,3:0:0:0: -356,228,113966,1,2,0:0:0:0: -276,188,114201,2,0,L|200:192,1,72,8|0,0:3|0:0,0:0:0:0: -144,128,114670,6,0,P|120:168|160:268,1,144,4|2,0:0|1:2,0:0:0:0: -224,292,115373,1,0,0:0:0:0: -152,344,115607,2,0,L|80:348,1,72,4|0,0:3|3:0,0:0:0:0: -224,292,116076,2,0,L|296:296,1,72,2|0,1:2|0:0,0:0:0:0: -380,312,116545,6,0,P|340:296|312:200,1,144,4|2,0:3|1:2,0:0:0:0: -369,143,117248,1,0,0:0:0:0: -447,182,117482,2,0,P|425:210|393:227,1,72,4|0,0:3|3:0,0:0:0:0: -380,60,117951,2,0,P|414:72|440:96,1,72,2|0,1:2|0:0,0:0:0:0: -369,143,118420,6,0,L|224:144,1,144,4|2,0:3|1:2,0:0:0:0: -140,144,119123,1,0,0:0:0:0: -182,70,119357,2,0,P|194:34|190:-6,1,72,4|0,0:3|3:0,0:0:0:0: -60,120,119826,2,0,P|59:156|70:190,1,72,2|0,1:2|0:0,0:0:0:0: -128,256,120295,6,0,B|176:240|176:240|268:256,1,144,4|2,0:3|1:2,0:0:0:0: -348,224,120998,2,0,P|352:268|344:296,1,72,0|4,0:0|0:3,0:0:0:0: -268,256,121466,2,0,P|264:212|272:184,1,72,0|2,3:0|1:2,0:0:0:0: -344,136,121935,1,0,0:0:0:0: -392,64,122170,6,0,P|316:56|260:112,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: -432,140,123341,1,0,3:0:0:0: -412,224,123576,2,0,L|488:228,1,72,2|0,1:2|0:0,0:0:0:0: -332,256,124045,6,0,P|263:246|208:290,1,144,4|2,0:3|1:2,0:0:0:0: -286,327,124748,1,0,0:0:0:0: -208,290,124982,2,0,L|212:218,1,72,4|0,0:3|3:0,0:0:0:0: -135,181,125451,2,0,L|124:252,1,72,2|0,1:2|0:0,0:0:0:0: -44,204,125920,6,0,L|80:56,1,144,4|2,0:3|1:2,0:0:0:0: -236,132,126857,2,0,P|304:136|372:104,1,144,4|2,0:3|1:2,0:0:0:0: -428,40,127560,1,0,0:0:0:0: -456,124,127795,6,0,L|460:196,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -344,256,128732,2,0,L|340:328,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -268,216,129435,1,0,0:0:0:0: -180,220,129670,5,4,2:0:0:0: -256,40,130373,1,2,0:0:0:0: -64,68,131076,1,2,0:0:0:0: -92,136,131310,1,0,0:0:0:0: -64,204,131545,6,0,L|60:288,1,72 -31,343,132248,2,0,P|86:345|127:309,1,108 -332,220,133420,5,2,0:0:0:0: -256,40,134123,1,2,0:0:0:0: -448,68,134826,1,2,0:0:0:0: -420,136,135060,1,0,0:0:0:0: -448,204,135295,6,0,L|452:288,1,72,2|0,0:0|0:0,0:0:0:0: -480,343,135998,2,0,P|426:345|385:309,1,108 -256,192,137170,5,2,0:0:0:0: -156,360,137873,1,2,0:0:0:0: -356,360,138576,2,0,L|352:308,1,36,2|0,0:0|0:0,0:0:0:0: -304,268,139045,6,0,P|336:253|372:252,1,72 -448,260,139748,2,0,L|444:152,1,108 -256,192,140920,5,2,0:0:0:0: -356,24,141623,1,2,0:0:0:0: -156,24,142326,2,0,L|160:72,1,36,2|0,0:0|0:0,0:0:0:0: -208,116,142795,6,0,P|176:131|140:132,1,72,2|0,0:0|0:0,0:0:0:0: -64,124,143498,2,0,L|68:232,1,108 -68,232,144670,5,4,0:3:0:0: -216,320,145138,1,4,0:3:0:0: -304,172,145607,1,4,0:3:0:0: -156,84,146075,1,4,0:3:0:0: -296,320,146545,5,4,0:3:0:0: -208,172,147013,1,4,0:3:0:0: -356,84,147482,1,4,0:3:0:0: -444,232,147950,1,4,0:3:0:0: -296,320,148420,6,0,P|252:328|192:296,2,108.000004119873,4|4|4,0:3|0:3|0:3,0:0:0:0: -260,248,149591,1,0,0:0:0:0: -320,196,149826,2,0,L|316:140,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: -120,236,159670,6,0,L|176:232,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: -160,152,160138,2,0,L|104:156,1,54.0000020599366,2|0,0:0|0:0,0:0:0:0: -240,180,160607,2,0,P|292:188|344:172,1,108.000004119873,4|2,0:3|0:0,3:0:0:0: -408,120,161310,1,0,3:0:0:0: -424,200,161545,6,0,L|420:256,1,54.0000020599366,4|0,0:3|0:0,0:0:0:0: -376,320,162013,2,0,P|396:328|480:304,2,108.000004119873,2|6|2,2:0|0:3|2:0,3:0:0:0: -312,268,163185,1,0,0:0:0:0: -296,348,163420,6,0,L|240:344,1,54.0000020599366,4|0,3:0|3:0,0:0:0:0: -160,320,163888,2,0,L|100:316,1,57.6,4|0,3:0|3:0,0:0:0:0: -64,232,164357,6,0,L|128:228,1,61.2000011672974,4|0,3:0|3:0,0:0:0:0: -204,200,164825,2,0,L|268:196,1,61.2000011672974,4|0,3:0|3:0,0:0:0:0: -232,108,165295,6,0,L|164:104,1,68.399998173523,4|0,3:0|3:0,0:0:0:0: -80,84,165763,2,0,L|4:80,1,72,4|0,3:0|3:0,0:0:0:0: -324,120,167170,6,0,P|388:128|456:92,1,144,4|2,0:0|1:2,0:0:0:0: -496,168,167873,1,0,0:0:0:0: -496,168,168107,2,0,P|484:204|488:256,1,72,4|0,0:3|3:0,0:0:0:0: -408,296,168576,2,0,P|398:261|378:231,1,72,2|0,1:2|0:0,0:0:0:0: -296,200,169045,6,0,B|228:228|156:204,1,144,4|2,0:3|1:2,0:0:0:0: -84,156,169748,1,0,0:0:0:0: -80,244,169982,2,0,L|76:316,1,72,4|0,0:3|3:0,0:0:0:0: -170,274,170451,2,0,L|156:204,1,72,2|0,1:2|0:0,0:0:0:0: -216,140,170920,6,0,L|284:276,1,144,4|2,0:3|1:2,0:0:0:0: -320,344,171623,1,0,0:0:0:0: -372,276,171857,2,0,P|366:240|349:207,1,72,4|0,0:3|3:0,0:0:0:0: -312,132,172326,2,0,L|276:60,1,72,2|0,1:2|0:0,0:0:0:0: -208,20,172795,6,0,P|272:36|348:12,1,144,4|2,0:3|1:2,0:0:0:0: -424,48,173498,2,0,L|412:132,1,72,0|4,0:0|0:3,0:0:0:0: -484,168,173966,2,0,L|472:252,1,72,0|2,3:0|1:2,0:0:0:0: -400,280,174435,1,0,0:0:0:0: -346,348,174670,6,0,P|414:363|472:324,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: -312,268,175841,1,0,3:0:0:0: -256,336,176076,2,0,L|184:332,1,72,2|0,1:2|0:0,0:0:0:0: -80,244,176545,6,0,B|140:248|140:248|164:244|164:244|223:247,1,144,4|2,0:3|1:2,0:0:0:0: -312,268,177248,1,0,0:0:0:0: -224,247,177482,2,0,P|240:215|272:187,1,72,4|0,0:3|3:0,0:0:0:0: -204,131,177951,2,0,P|233:111|275:103,1,72,2|0,1:2|0:0,0:0:0:0: -240,23,178420,6,0,B|280:15|316:35|316:35|376:71,1,144,4|2,0:3|1:2,0:0:0:0: -399,236,179357,2,0,B|359:244|323:224|323:224|263:188,1,144,4|2,0:3|1:2,0:0:0:0: -204,132,180060,1,0,0:0:0:0: -184,216,180295,6,0,L|188:288,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -120,156,180998,1,0,0:0:0:0: -56,96,181232,2,0,L|60:24,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: -36,180,181935,1,0,0:0:0:0: -100,240,182170,6,0,P|144:300|116:380,2,144,4|2|4,0:0|1:2|0:3,0:0:0:0: -60,316,183341,1,0,0:0:0:0: -220,352,183576,2,0,L|308:348,1,72,2|0,1:2|0:0,0:0:0:0: -396,264,184045,6,0,B|336:268|336:268|312:264|312:264|253:267,1,144,4|2,0:3|1:2,0:0:0:0: -253,267,184748,1,0,0:0:0:0: -268,180,184982,2,0,L|339:177,1,72,4|0,0:3|0:0,0:0:0:0: -164,280,185451,2,0,L|92:282,1,72,2|0,1:2|0:0,0:0:0:0: -52,208,185920,6,0,P|8:268|32:344,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: -140,212,187091,1,0,0:0:0:0: -92,284,187326,2,0,P|104:316|100:368,1,72,2|0,1:2|0:0,0:0:0:0: -52,208,187795,6,0,P|48:136|76:72,1,144,4|2,0:3|1:2,0:0:0:0: -160,52,188498,2,0,P|188:28|220:16,1,72,0|4,0:0|0:3,0:0:0:0: -232,100,188966,2,0,P|268:93|301:98,1,72,0|2,0:0|1:2,0:0:0:0: -372,152,189435,1,0,0:0:0:0: -420,224,189670,6,0,P|428:296|400:360,2,144,4|2|4,0:3|1:2|0:3,0:0:0:0: -372,152,190841,1,0,0:0:0:0: -392,68,191076,2,0,L|465:64,1,72,2|0,1:2|0:0,0:0:0:0: -304,92,191545,6,0,P|236:104|168:76,1,144,4|2,0:3|1:2,0:0:0:0: -108,12,192248,1,0,0:0:0:0: -168,76,192482,2,0,L|172:152,1,72,4|0,0:3|0:0,0:0:0:0: -80,136,192951,2,0,L|101:204,1,72,2|0,1:2|0:0,0:0:0:0: -12,220,193420,6,0,B|50:279|50:279|80:300|120:292,1,144,4|2,0:3|1:2,0:0:0:0: -284,232,194357,2,0,B|320:221|352:244|352:244|384:267|420:256,1,144,4|2,0:3|1:2,0:0:0:0: -488,200,195060,1,0,0:0:0:0: -507,284,195295,6,0,P|492:315|464:338,1,72,4|0,0:0|0:0,0:0:0:0: -380,356,195763,2,0,L|236:352,1,144,0|4,1:0|0:3,0:0:0:0: -152,328,196466,1,0,3:0:0:0: -64,336,196701,2,0,P|29:325|4:300,1,72,0|0,1:0|0:0,0:0:0:0: -76,252,197170,6,0,P|108:188|96:116,1,144,4|0,0:0|1:0,0:0:0:0: -36,56,197873,1,2,0:0:0:0: -120,32,198107,2,0,L|192:28,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -248,152,199045,6,0,P|280:168|304:196,1,72,4|2,0:3|0:0,0:0:0:0: -336,277,199513,2,0,P|306:296|269:303,1,72,2|0,1:2|0:0,0:0:0:0: -183,290,199982,2,0,P|180:254|193:219,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: -436,252,200920,6,0,P|404:188|416:116,1,144,4|0,0:3|1:0,0:0:0:0: -476,56,201623,1,2,0:0:0:0: -392,32,201857,2,0,L|320:28,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: -264,152,202795,6,0,P|232:168|208:196,1,72,4|2,0:3|0:0,0:0:0:0: -176,277,203263,2,0,P|205:296|242:303,1,72,2|0,1:2|0:0,0:0:0:0: -329,290,203732,2,0,P|331:254|318:219,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: -72,324,204670,6,0,B|60:272|60:272|76:180,1,144,4|0,0:0|1:0,0:0:0:0: -92,96,205373,1,2,0:0:0:0: -8,124,205607,2,0,P|5:88|14:53,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -168,192,206545,6,0,P|200:174|237:173,1,72,4|2,0:3|0:0,0:0:0:0: -320,160,207013,2,0,P|318:196|301:229,1,72,2|0,1:2|0:0,0:0:0:0: -272,307,207482,2,0,P|240:287|221:256,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: -440,324,208420,6,0,B|452:272|452:272|436:180,1,144,4|0,0:3|1:0,0:0:0:0: -420,96,209123,1,2,0:0:0:0: -504,124,209357,2,0,P|507:88|498:53,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: -344,192,210295,6,0,P|311:174|274:173,1,72,4|2,0:3|0:0,0:0:0:0: -190,156,210763,2,0,P|191:192|208:225,1,72,2|0,1:2|0:0,0:0:0:0: -288,256,211232,1,4,0:3:0:0: -132,332,211701,1,0,1:0:0:0: -28,192,212170,6,0,P|16:120|44:56,1,144,4|0,0:0|1:0,0:0:0:0: -120,16,212873,1,2,0:0:0:0: -204,32,213107,2,0,L|304:28,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -192,204,214045,6,0,P|196:240|216:272,1,72,4|2,0:3|0:0,0:0:0:0: -298,241,214513,2,0,P|327:219|345:186,1,72,6|0,1:2|0:0,0:0:0:0: -280,132,214982,2,0,P|246:117|209:118,2,72,4|2|0,0:3|0:0|1:0,0:0:0:0: -484,192,215920,6,0,P|496:120|468:56,1,144,4|0,0:3|1:0,0:0:0:0: -392,16,216623,1,2,0:0:0:0: -308,32,216857,2,0,L|208:28,2,72,4|0|2,0:3|0:0|1:2,0:0:0:0: -320,204,217795,6,0,P|316:240|296:272,1,72,4|2,0:3|0:0,0:0:0:0: -213,241,218263,2,0,P|184:219|166:186,1,72,2|0,1:2|0:0,0:0:0:0: -232,132,218732,2,0,B|260:112|300:116|300:116|384:128,1,144,4|0,0:3|1:0,0:0:0:0: -348,336,219670,6,0,B|320:356|280:352|280:352|196:340,1,144,4|0,0:0|1:0,0:0:0:0: -124,328,220373,1,2,0:0:0:0: -54,276,220607,2,0,P|41:308|39:345,2,72,4|2|2,0:3|0:0|1:2,0:0:0:0: -156,80,221545,6,0,L|251:94,1,72,4|2,0:3|0:0,0:0:0:0: -212,169,222013,2,0,L|148:160,1,64.799998022461,2|0,1:2|0:0,0:0:0:0: -140,240,222482,2,0,L|216:252,2,57.6,4|2|0,0:3|0:0|1:0,0:0:0:0: -256,192,223420,12,0,227170,0:0:0:0: -"; } } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index c29bc91d17..2504c9c62c 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -73,10 +73,10 @@ namespace osu.Game.Users public struct UserRanks { [JsonProperty(@"global")] - public int Global; + public int? Global; [JsonProperty(@"country")] - public int Country; + public int? Country; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 14d2381640..86833a6d19 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -287,6 +287,7 @@ + @@ -376,6 +377,7 @@ + @@ -409,14 +411,14 @@ - - - - - - - - + + + + + + + + @@ -894,11 +896,13 @@ + +