diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 35bf9e7a0e..f67d7a8c4e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,63 +2,70 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", - "command": "msbuild", - "type": "shell", - "suppressTaskName": true, - "args": [ - "/property:GenerateFullPaths=true", - "/property:DebugType=portable", - "/verbosity:minimal", - "/m" //parallel compiling support. - ], "tasks": [{ - "taskName": "Build (Debug)", + "label": "Build (Debug)", + "type": "shell", + "command": "msbuild", + "args": [ + "/p:GenerateFullPaths=true", + "/p:DebugType=portable", + "/m", + "/v:m" + ], "group": { "kind": "build", "isDefault": true }, - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Build (Release)", + "label": "Build (Release)", + "type": "shell", + "command": "msbuild", + "args": [ + "/p:Configuration=Release", + "/p:DebugType=portable", + "/p:GenerateFullPaths=true", + "/m", + "/v:m" + ], "group": "build", - "args": [ - "/property:Configuration=Release" - ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean (Debug)", + "label": "Clean (Debug)", + "type": "shell", + "command": "msbuild", "args": [ - "/target:Clean" + "/p:DebugType=portable", + "/p:GenerateFullPaths=true", + "/m", + "/t:Clean", + "/v:m" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean (Release)", + "label": "Clean (Release)", + "type": "shell", + "command": "msbuild", "args": [ - "/target:Clean", - "/property:Configuration=Release" + "/p:Configuration=Release", + "/p:GenerateFullPaths=true", + "/p:DebugType=portable", + "/m", + "/t:Clean", + "/v:m" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean All", + "label": "Clean All", "dependsOn": [ "Clean (Debug)", "Clean (Release)" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" } ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index b2f7fdabfc..9901dbde18 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -1,8 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -18,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps CatchHitObject lastObj = null; + initialiseHyperDash(beatmap.HitObjects); + foreach (var obj in beatmap.HitObjects) { if (obj.NewCombo) @@ -34,5 +42,49 @@ namespace osu.Game.Rulesets.Catch.Beatmaps lastObj = obj; } } + + private void initialiseHyperDash(List objects) + { + // todo: add difficulty adjust. + double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2; + + int lastDirection = 0; + double lastExcess = halfCatcherWidth; + + int objCount = objects.Count; + + for (int i = 0; i < objCount - 1; i++) + { + CatchHitObject currentObject = objects[i]; + + // not needed? + // if (currentObject is TinyDroplet) continue; + + CatchHitObject nextObject = objects[i + 1]; + + // while (nextObject is TinyDroplet) + // { + // if (++i == objCount - 1) break; + // nextObject = objects[i + 1]; + // } + + int thisDirection = nextObject.X > currentObject.X ? 1 : -1; + double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4; + double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); + + if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext) + { + currentObject.HyperDashTarget = nextObject; + lastExcess = halfCatcherWidth; + } + else + { + //currentObject.DistanceToHyperDash = timeToNext - distanceToNext; + lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth); + } + + lastDirection = thisDirection; + } + } } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 1d5fc0545e..cb46c75583 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Catch public override string Description => "osu!catch"; + public override string ShortName => "fruits"; + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index cb4e6453ce..9952e85c70 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -27,9 +27,19 @@ namespace osu.Game.Rulesets.Catch.Objects public float Scale { get; set; } = 1; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + /// + /// Whether this fruit can initiate a hyperdash. + /// + public bool HyperDash => HyperDashTarget != null; + + /// + /// The target fruit if we are to initiate a hyperdash. + /// + public CatchHitObject HyperDashTarget; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 4c28a9d021..9f46bbd3a4 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -70,6 +71,20 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } } }; + + if (HitObject.HyperDash) + { + Add(new Pulp + { + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.Red, + Blending = BlendingMode.Additive, + Alpha = 0.5f, + Scale = new Vector2(2) + }); + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index bfb674d1b4..db0632a07d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable RelativeChildSize = new Vector2(1, (float)HitObject.Duration) }; - foreach (CatchHitObject tick in s.Ticks) + foreach (CatchHitObject tick in s.NestedHitObjects.OfType()) { TinyDroplet tiny = tick as TinyDroplet; if (tiny != null) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index bf9f0bd44b..7d0d80a0ce 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using OpenTK; -using osu.Framework.Lists; namespace osu.Game.Rulesets.Catch.Objects { @@ -29,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -42,92 +41,94 @@ namespace osu.Game.Rulesets.Catch.Objects TickDistance = scoringDistance / difficulty.SliderTickRate; } - public IEnumerable Ticks + protected override void CreateNestedHitObjects() { - get + base.CreateNestedHitObjects(); + + createTicks(); + } + + private void createTicks() + { + if (TickDistance == 0) + return; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + AddNested(new Fruit { - SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime)); + Samples = Samples, + ComboColour = ComboColour, + StartTime = StartTime, + X = X + }); - if (TickDistance == 0) - return ticks; + for (var repeat = 0; repeat < RepeatCount; repeat++) + { + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; - var length = Curve.Distance; - var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - ticks.Add(new Fruit + for (var d = tickDistance; d <= length; d += tickDistance) { - Samples = Samples, - ComboColour = ComboColour, - StartTime = StartTime, - X = X - }); + if (d > length - minDistanceFromEnd) + break; - for (var repeat = 0; repeat < RepeatCount; repeat++) - { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + var timeProgress = d / length; + var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - for (var d = tickDistance; d <= length; d += tickDistance) + var lastTickTime = repeatStartTime + timeProgress * repeatDuration; + AddNested(new Droplet { - if (d > length - minDistanceFromEnd) - break; - - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - - var lastTickTime = repeatStartTime + timeProgress * repeatDuration; - ticks.Add(new Droplet - { - StartTime = lastTickTime, - ComboColour = ComboColour, - X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - double tinyTickInterval = tickDistance / length * repeatDuration; - while (tinyTickInterval > 100) - tinyTickInterval /= 2; - - for (double t = 0; t < repeatDuration; t += tinyTickInterval) - { - double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; - - ticks.Add(new TinyDroplet - { - StartTime = repeatStartTime + t, - ComboColour = ComboColour, - X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - ticks.Add(new Fruit - { - Samples = Samples, + StartTime = lastTickTime, ComboColour = ComboColour, - StartTime = repeatStartTime + repeatDuration, - X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) }); } - return ticks; + double tinyTickInterval = tickDistance / length * repeatDuration; + while (tinyTickInterval > 100) + tinyTickInterval /= 2; + + for (double t = 0; t < repeatDuration; t += tinyTickInterval) + { + double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; + + AddNested(new TinyDroplet + { + StartTime = repeatStartTime + t, + ComboColour = ComboColour, + X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + AddNested(new Fruit + { + Samples = Samples, + ComboColour = ComboColour, + StartTime = repeatStartTime + repeatDuration, + X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + }); } + } + public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; @@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.Objects set { Curve.ControlPoints = value; } } - public List RepeatSamples { get; set; } = new List(); + public List> RepeatSamples { get; set; } = new List>(); public CurveType CurveType { diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 0806c4b29d..3826fd1129 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Scoring AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - foreach (var unused in stream.Ticks) + foreach (var unused in stream.NestedHitObjects.OfType()) AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); continue; diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs index 538f6930ed..8217265e3d 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseCatcherArea : OsuTestCase + public class TestCaseCatcherArea : OsuTestCase { private RulesetInfo catchRuleset; + private TestCatcherArea catcherArea; public override IReadOnlyList RequiredTypes => new[] { @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCaseCatcherArea() { AddSliderStep("CircleSize", 0, 8, 5, createCatcher); + AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); } private void createCatcher(float size) @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests Child = new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, - Child = new CatcherArea(new BeatmapDifficulty { CircleSize = size }) + Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft @@ -46,5 +48,15 @@ namespace osu.Game.Rulesets.Catch.Tests { catchRuleset = rulesets.GetRuleset(2); } + + private class TestCatcherArea : CatcherArea + { + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) + { + } + + public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1; + } } } diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs new file mode 100644 index 0000000000..ce3f79bae2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + [Ignore("getting CI working")] + public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer + { + public TestCaseHyperdash() + : base(typeof(CatchRuleset)) + { + } + + protected override Beatmap CreateBeatmap() + { + var beatmap = new Beatmap(); + + for (int i = 0; i < 512; i++) + if (i % 5 < 3) + beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 6fd0793500..76dbfa77c6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : ScrollingPlayfield { - public static readonly float BASE_WIDTH = 512; + public const float BASE_WIDTH = 512; protected override Container Content => content; private readonly Container content; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.UI }; } - public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.CanCatch(obj); + public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); public override void Add(DrawableHitObject h) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 203db1bb8c..2bb0f3cd18 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { @@ -21,18 +22,18 @@ namespace osu.Game.Rulesets.Catch.UI { public const float CATCHER_SIZE = 172; - private readonly Catcher catcher; + protected readonly Catcher MovableCatcher; public Container ExplodingFruitTarget { - set { catcher.ExplodingFruitTarget = value; } + set { MovableCatcher.ExplodingFruitTarget = value; } } public CatcherArea(BeatmapDifficulty difficulty = null) { RelativeSizeAxes = Axes.X; Height = CATCHER_SIZE; - Child = catcher = new Catcher(difficulty) + Child = MovableCatcher = new Catcher(difficulty) { AdditiveTarget = this, }; @@ -41,17 +42,17 @@ namespace osu.Game.Rulesets.Catch.UI public void Add(DrawableHitObject fruit, Vector2 absolutePosition) { fruit.RelativePositionAxes = Axes.None; - fruit.Position = new Vector2(catcher.ToLocalSpace(absolutePosition).X - catcher.DrawSize.X / 2, 0); + fruit.Position = new Vector2(MovableCatcher.ToLocalSpace(absolutePosition).X - MovableCatcher.DrawSize.X / 2, 0); fruit.Anchor = Anchor.TopCentre; fruit.Origin = Anchor.BottomCentre; fruit.Scale *= 0.7f; fruit.LifetimeEnd = double.MaxValue; - catcher.Add(fruit); + MovableCatcher.Add(fruit); } - public bool CanCatch(CatchHitObject obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X * Math.Abs(catcher.Scale.X) / DrawSize.X / 2; + public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); public class Catcher : Container, IKeyBindingHandler { @@ -105,14 +106,35 @@ namespace osu.Game.Rulesets.Catch.UI dashing = value; - if (dashing) - Schedule(addAdditiveSprite); + Trail |= dashing; } } - private void addAdditiveSprite() + private bool trail; + + /// + /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met. + /// + protected bool Trail { - if (!dashing || AdditiveTarget == null) return; + get { return trail; } + set + { + if (value == trail) return; + + trail = value; + + if (Trail) + beginTrail(); + } + } + + private void beginTrail() + { + Trail &= dashing || HyperDashing; + Trail &= AdditiveTarget != null; + + if (!Trail) return; var additive = createCatcherSprite(); @@ -120,6 +142,7 @@ namespace osu.Game.Rulesets.Catch.UI additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. additive.Position = Position; additive.Scale = Scale; + additive.Colour = HyperDashing ? Color4.Red : Color4.White; additive.RelativePositionAxes = RelativePositionAxes; additive.Blending = BlendingMode.Additive; @@ -127,7 +150,7 @@ namespace osu.Game.Rulesets.Catch.UI additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); - Scheduler.AddDelayed(addAdditiveSprite, 50); + Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); } private Sprite createCatcherSprite() => new Sprite @@ -138,6 +161,10 @@ namespace osu.Game.Rulesets.Catch.UI OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. }; + /// + /// Add a caught fruit to the catcher's stack. + /// + /// The fruit that was caught. public void Add(DrawableHitObject fruit) { float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; @@ -150,10 +177,80 @@ namespace osu.Game.Rulesets.Catch.UI caughtFruit.Add(fruit); - if (((CatchHitObject)fruit.HitObject).LastInCombo) + var catchObject = (CatchHitObject)fruit.HitObject; + + if (catchObject.LastInCombo) explode(); } + /// + /// Let the catcher attempt to catch a fruit. + /// + /// The fruit to catch. + /// Whether the catch is possible. + public bool AttemptCatch(CatchHitObject fruit) + { + const double relative_catcher_width = CATCHER_SIZE / 2; + + // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. + var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; + var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; + + var validCatch = + catchObjectPosition >= catcherPosition - relative_catcher_width / 2 && + catchObjectPosition <= catcherPosition + relative_catcher_width / 2; + + if (validCatch && fruit.HyperDash) + { + HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED; + HyperDashDirection = fruit.HyperDashTarget.X - fruit.X; + } + else + HyperDashModifier = 1; + + return validCatch; + } + + /// + /// Whether we are hypderdashing or not. + /// + public bool HyperDashing => hyperDashModifier != 1; + + private double hyperDashModifier = 1; + + /// + /// The direction in which hyperdash is allowed. 0 allows both directions. + /// + public double HyperDashDirection; + + /// + /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1. + /// + public double HyperDashModifier + { + get { return hyperDashModifier; } + set + { + if (value == hyperDashModifier) return; + hyperDashModifier = value; + + const float transition_length = 180; + + if (HyperDashing) + { + this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint); + this.FadeTo(0.2f, transition_length, Easing.OutQuint); + Trail = true; + } + else + { + HyperDashDirection = 0; + this.FadeColour(Color4.White, transition_length, Easing.OutQuint); + this.FadeTo(1, transition_length, Easing.OutQuint); + } + } + } + public bool OnPressed(CatchAction action) { switch (action) @@ -201,10 +298,15 @@ namespace osu.Game.Rulesets.Catch.UI if (currentDirection == 0) return; + var direction = Math.Sign(currentDirection); + double dashModifier = Dashing ? 1 : 0.5; - Scale = new Vector2(Math.Abs(Scale.X) * Math.Sign(currentDirection), Scale.Y); - X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); + if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection))) + dashModifier = hyperDashModifier; + + Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); + X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); } private void explode() diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index d5a799b4ed..407d4db143 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The time to retrieve the sample info list from. /// - private SampleInfoList sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 270c264e0c..8251dea5f7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private SampleInfoList sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index c353496410..8e832960df 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; @@ -76,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Duration = endTime - HitObject.StartTime }; - hold.Head.Samples.Add(new SampleInfo - { - Name = SampleInfo.HIT_NORMAL - }); + if (hold.Head.Samples == null) + hold.Head.Samples = new List(); + + hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); hold.Tail.Samples = HitObject.Samples; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 4eea884891..070c7b09d1 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania public override string Description => "osu!mania"; + public override string ShortName => "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); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 237df72480..7b207ca229 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } }); - foreach (var tick in HitObject.Ticks) + foreach (var tick in HitObject.NestedHitObjects.OfType()) { var drawableTick = new DrawableHoldNoteTick(tick) { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index fc1331551e..f72bed3142 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Types; @@ -63,9 +62,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; @@ -74,29 +73,27 @@ namespace osu.Game.Rulesets.Mania.Objects Tail.ApplyDefaults(controlPointInfo, difficulty); } - /// - /// The scoring scoring ticks of the hold note. - /// - public IEnumerable Ticks => ticks ?? (ticks = createTicks()); - private List ticks; - - private List createTicks() + protected override void CreateNestedHitObjects() { - var ret = new List(); + base.CreateNestedHitObjects(); + createTicks(); + } + + private void createTicks() + { if (tickSpacing == 0) - return ret; + return; for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) { - ret.Add(new HoldNoteTick + AddNested(new HoldNoteTick { StartTime = t, Column = Column }); } - return ret; } /// @@ -110,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// private const double release_window_lenience = 1.5; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindows *= release_window_lenience; } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 3c4ff4216f..51a9a18afa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Judgements; @@ -15,11 +16,12 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// The key-press hit window for this note. /// + [JsonIgnore] public HitWindows HitWindows { get; protected set; } = new HitWindows(); - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindows = new HitWindows(difficulty.OverallDifficulty); } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 9b8ebe0070..012137f555 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Scoring AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); // Ticks - int tickCount = holdNote.Ticks.Count(); + int tickCount = holdNote.NestedHitObjects.OfType().Count(); for (int i = 0; i < tickCount; i++) AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); } diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs index 484ce77a16..03886f5784 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseManiaHitObjects : OsuTestCase + public class TestCaseManiaHitObjects : OsuTestCase { public TestCaseManiaHitObjects() { diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index aab784f177..1932038411 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseManiaPlayfield : OsuTestCase + public class TestCaseManiaPlayfield : OsuTestCase { private const double start_time = 500; private const double duration = 500; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index cbbcb84b31..61446a31b6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.Lists; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - SortedList timingPoints = Beatmap.ControlPointInfo.TimingPoints; + var timingPoints = Beatmap.ControlPointInfo.TimingPoints; var barLines = new List(); for (int i = 0; i < timingPoints.Count; i++) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs new file mode 100644 index 0000000000..0d7fa6c334 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditPlayfield : OsuPlayfield + { + protected override CursorContainer CreateCursor() => null; + + protected override bool ProxyApproachCircles => false; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs new file mode 100644 index 0000000000..1e9e4b4686 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditRulesetContainer : OsuRulesetContainer + { + public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs new file mode 100644 index 0000000000..ec3aa4661c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuHitObjectComposer : HitObjectComposer + { + public OsuHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true); + + protected override IReadOnlyList CompositionTools => new ICompositionTool[] + { + new HitObjectCompositionTool(), + new HitObjectCompositionTool(), + new HitObjectCompositionTool() + }; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 74454ca555..befe84e3e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Scale = s.Scale, ComboColour = s.ComboColour, Samples = s.Samples, + SampleControlPoint = s.SampleControlPoint }) }; @@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(initialCircle); var repeatDuration = s.Curve.Distance / s.Velocity; - foreach (var tick in s.Ticks) + foreach (var tick in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); @@ -82,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(drawableTick); } - foreach (var repeatPoint in s.RepeatPoints) + foreach (var repeatPoint in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); @@ -165,6 +167,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } public Drawable ProxiedLayer => initialCircle.ApproachCircle; + + public override Vector2 SelectionPoint => ToScreenSpace(body.Position); + public override Quad SelectionQuad => body.PathDrawQuad; } internal interface ISliderProgress diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 2082e9a27b..75c2c15084 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -14,6 +14,7 @@ using osu.Game.Configuration; using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; + private int textureWidth => (int)PathWidth * 2; private readonly Slider slider; @@ -182,4 +185,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces SetRange(start, end); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index e6bfd8a277..7532387aa2 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects return HitResult.Miss; } - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 39ec753fe1..5f9f11c783 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List RepeatSamples { get; set; } = new List(); + public List> RepeatSamples { get; set; } = new List>(); public int RepeatCount { get; set; } = 1; private int stackHeight; @@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Objects public double Velocity; public double TickDistance; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -99,75 +99,79 @@ namespace osu.Game.Rulesets.Osu.Objects public int RepeatAt(double progress) => (int)(progress * RepeatCount); - public IEnumerable Ticks + protected override void CreateNestedHitObjects() { - get + base.CreateNestedHitObjects(); + + createTicks(); + createRepeatPoints(); + } + + private void createTicks() + { + if (TickDistance == 0) return; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + for (var repeat = 0; repeat < RepeatCount; repeat++) { - if (TickDistance == 0) yield break; + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; - var length = Curve.Distance; - var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - for (var repeat = 0; repeat < RepeatCount; repeat++) + for (var d = tickDistance; d <= length; d += tickDistance) { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + if (d > length - minDistanceFromEnd) + break; - for (var d = tickDistance; d <= length; d += tickDistance) + var distanceProgress = d / length; + var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; + + AddNested(new SliderTick { - if (d > length - minDistanceFromEnd) - break; - - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - yield return new SliderTick + RepeatIndex = repeat, + StartTime = repeatStartTime + timeProgress * repeatDuration, + Position = Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + ComboColour = ComboColour, + Samples = new List(Samples.Select(s => new SampleInfo { - RepeatIndex = repeat, - StartTime = repeatStartTime + timeProgress * repeatDuration, - Position = Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - ComboColour = ComboColour, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }; - } + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); } } } - public IEnumerable RepeatPoints + + private void createRepeatPoints() { - get + var length = Curve.Distance; + var repeatPointDistance = Math.Min(Distance, length); + var repeatDuration = length / Velocity; + + for (var repeat = 1; repeat < RepeatCount; repeat++) { - var length = Curve.Distance; - var repeatPointDistance = Math.Min(Distance, length); - var repeatDuration = length / Velocity; - - for (var repeat = 1; repeat < RepeatCount; repeat++) + for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) { - for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) - { - var repeatStartTime = StartTime + repeat * repeatDuration; - var distanceProgress = d / length; + var repeatStartTime = StartTime + repeat * repeatDuration; + var distanceProgress = d / length; - yield return new RepeatPoint - { - RepeatIndex = repeat, - StartTime = repeatStartTime, - Position = Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - ComboColour = ComboColour, - }; - } + AddNested(new RepeatPoint + { + RepeatIndex = repeat, + StartTime = repeatStartTime, + Position = Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + ComboColour = ComboColour, + Samples = new List(RepeatSamples[repeat]) + }); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index c4f5dfe97a..7d1bd9239d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects public override bool NewCombo => true; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 972677a6f1..11c0df0c8c 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { + // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value; float dist = diff.Length; @@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing } }); - var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t); + var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime); foreach (var time in scoringTimes) computeVertex(time); computeVertex(slider.EndTime); diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index a65d28cec0..56990d1351 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; @@ -9,6 +10,8 @@ namespace osu.Game.Rulesets.Osu { public class OsuInputManager : RulesetInputManager { + public IEnumerable PressedActions => KeyBindingContainer.PressedActions; + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fdf2a458b7..19d60c9046 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -16,6 +15,10 @@ using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu { @@ -31,21 +34,35 @@ namespace osu.Game.Rulesets.Osu new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; - public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) { - new BeatmapStatistic + IEnumerable hitObjects = beatmap.Beatmap.HitObjects; + IEnumerable circles = hitObjects.Where(c => !(c is IHasEndTime)); + IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); + IEnumerable spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve)); + + return new[] { - Name = @"Circle count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is HitCircle).ToString(), - Icon = FontAwesome.fa_dot_circle_o - }, - new BeatmapStatistic - { - Name = @"Slider count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is Slider).ToString(), - Icon = FontAwesome.fa_circle_o - } - }; + new BeatmapStatistic + { + Name = @"Circle Count", + Content = circles.Count().ToString(), + Icon = FontAwesome.fa_circle_o + }, + new BeatmapStatistic + { + Name = @"Slider Count", + Content = sliders.Count().ToString(), + Icon = FontAwesome.fa_circle + }, + new BeatmapStatistic + { + Name = @"Spinner Count", + Content = spinners.Count().ToString(), + Icon = FontAwesome.fa_circle + } + }; + } public override IEnumerable GetModsFor(ModType type) { @@ -118,8 +135,12 @@ namespace osu.Game.Rulesets.Osu public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); + public override string Description => "osu!"; + public override string ShortName => "osu"; + public override SettingsSubsection CreateSettings() => new OsuSettings(); public override int LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index cd6b6c5e27..2cf321da50 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Scoring countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); beatmapMaxCombo = Beatmap.HitObjects.Count; - beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.RepeatCount + s.Ticks.Count()); + beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1; } public override double Calculate(Dictionary categoryRatings = null) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 6e5dd18c46..ad9737af52 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -39,11 +40,11 @@ namespace osu.Game.Rulesets.Osu.Scoring AddJudgement(new OsuJudgement { Result = HitResult.Great }); // Ticks - foreach (var unused in slider.Ticks) + foreach (var unused in slider.NestedHitObjects.OfType()) AddJudgement(new OsuJudgement { Result = HitResult.Great }); //Repeats - foreach (var unused in slider.RepeatPoints) + foreach (var unused in slider.NestedHitObjects.OfType()) AddJudgement(new OsuJudgement { Result = HitResult.Great }); } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs index 99526b64ee..c4932d7803 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseHitObjects : OsuTestCase + public class TestCaseHitObjects : OsuTestCase { private FramedClock framedClock; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 793a9b73ad..cb8b13ef27 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { internal class CursorTrail : Drawable { - public override bool HandleInput => true; - private int currentIndex; private Shader shader; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 89f6a4e255..0dc6feee75 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -13,6 +13,7 @@ using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Rulesets.Osu.UI { @@ -24,12 +25,19 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ProvidingUserCursor => true; + // Todo: This should not be a thing, but is currently required for the editor + // https://github.com/ppy/osu-framework/issues/1283 + protected virtual bool ProxyApproachCircles => true; + public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public override Vector2 Size { get { + if (Parent == null) + return Vector2.Zero; + var parentSize = Parent.DrawSize; var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y); @@ -65,7 +73,10 @@ namespace osu.Game.Rulesets.Osu.UI protected override void LoadComplete() { base.LoadComplete(); - AddInternal(new GameplayCursor()); + + var cursor = CreateCursor(); + if (cursor != null) + AddInternal(cursor); } public override void Add(DrawableHitObject h) @@ -73,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI h.Depth = (float)h.HitObject.StartTime; var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null) + if (c != null && ProxyApproachCircles) approachCircles.Add(c.ProxiedLayer.CreateProxy()); base.Add(h); @@ -102,5 +113,7 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } + + protected virtual CursorContainer CreateCursor() => new GameplayCursor(); } } diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs new file mode 100644 index 0000000000..9d0037b97a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Taiko.Audio +{ + public class DrumSampleMapping + { + private readonly ControlPointInfo controlPoints; + private readonly Dictionary mappings = new Dictionary(); + + public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio) + { + this.controlPoints = controlPoints; + + IEnumerable samplePoints; + if (controlPoints.SamplePoints.Count == 0) + // Get the default sample point + samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; + else + samplePoints = controlPoints.SamplePoints; + + foreach (var s in samplePoints.Distinct()) + { + mappings[s] = new DrumSample + { + Centre = s.GetSampleInfo().GetChannel(audio.Sample), + Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample) + }; + } + } + + public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time)]; + + public class DrumSample + { + public SampleChannel Centre; + public SampleChannel Rim; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9b4a6c47a9..690b80b12c 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - SampleInfoList samples = obj.Samples; + List samples = obj.Samples; bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); @@ -115,12 +115,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List allSamples = curveData != null ? curveData.RepeatSamples : new List(new[] { samples }); + List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - SampleInfoList currentSamples = allSamples[i]; + List currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2396f3bf91..75e988ced6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeChildSize = new Vector2((float)HitObject.Duration, 1) }); - foreach (var tick in drumRoll.Ticks) + foreach (var tick in drumRoll.NestedHitObjects.OfType()) { var newTick = new DrawableDrumRollTick(tick); newTick.OnJudgement += onTickJudgement; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 489eacf386..fd35f0eaec 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { validKeyPressed = HitActions.Contains(action); + // Only count this as handled if the new judgement is a hit return UpdateJudgement(true); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index c07eaf4d8b..cda82afe0e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > second_hit_window) - AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Miss }); + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None }); return; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 738902846b..5ca33aaea2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -34,10 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; - private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim }; - private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; - private TaikoAction[] lastAction; - /// /// The amount of times the user has hit this swell. /// @@ -205,19 +201,20 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } + private bool? lastWasCentre; + public override bool OnPressed(TaikoAction action) { // Don't handle keys before the swell starts if (Time.Current < HitObject.StartTime) return false; - // Find the keyset which this key corresponds to - var keySet = rimActions.Contains(action) ? rimActions : centreActions; + var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre; - // Ensure alternating keysets - if (keySet == lastAction) + // Ensure alternating centre and rim hits + if (lastWasCentre == isCentre) return false; - lastAction = keySet; + lastWasCentre = isCentre; UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 7976cbbbc1..92da3fe09e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -6,6 +6,9 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; +using System.Linq; +using osu.Game.Audio; +using System.Collections.Generic; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -35,6 +38,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; } + // Normal and clap samples are handled by the drum + protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); + protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); public abstract bool OnPressed(TaikoAction action); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 526d23f51a..5a566fd091 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -37,53 +37,46 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double RequiredGreatHits { get; protected set; } - /// - /// Total number of drum roll ticks. - /// - public int TotalTicks => Ticks.Count(); - - /// - /// Initializes the drum roll ticks if not initialized and returns them. - /// - public IEnumerable Ticks => ticks ?? (ticks = createTicks()); - - private List ticks; - /// /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// private double tickSpacing = 100; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / TickRate; - RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); - RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); + RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); + RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); } - private List createTicks() + protected override void CreateNestedHitObjects() { - var ret = new List(); + base.CreateNestedHitObjects(); + createTicks(); + } + + private void createTicks() + { if (tickSpacing == 0) - return ret; + return; bool first = true; for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { - ret.Add(new DrumRollTick + AddNested(new DrumRollTick { FirstTick = first, TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", @@ -93,8 +86,6 @@ namespace osu.Game.Rulesets.Taiko.Objects first = false; } - - return ret; } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 03b9be4157..1b9b44fd99 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double HitWindowMiss = 95; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20); HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50); diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index f74a543ca9..cd2876ea2d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -16,4 +16,4 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index c3e5597f43..df1a19267f 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Taiko.Replays } else if (drumRoll != null) { - foreach (var tick in drumRoll.Ticks) + foreach (var tick in drumRoll.NestedHitObjects.OfType()) { Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); hitButton = !hitButton; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 0048566b15..df9ce5e2eb 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring } else if (obj is DrumRoll) { - for (int i = 0; i < ((DrumRoll)obj).TotalTicks; i++) + for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType().Count(); i++) { AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 99ae36967a..4de9ba0ce7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Taiko public override string Description => "osu!taiko"; + public override string ShortName => "taiko"; + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap); diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs index 555f9bb0da..b1e6e9c4ce 100644 --- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseTaikoPlayfield : OsuTestCase + public class TestCaseTaikoPlayfield : OsuTestCase { private const double default_duration = 1000; private const float scroll_time = 1000; @@ -165,11 +165,15 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addSwell(double duration = default_duration) { - rulesetContainer.Playfield.Add(new DrawableSwell(new Swell + var swell = new Swell { StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, Duration = duration, - })); + }; + + swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + rulesetContainer.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -184,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = duration, }; + d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); } @@ -195,6 +201,8 @@ namespace osu.Game.Rulesets.Taiko.Tests IsStrong = strong }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + if (strong) rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); else @@ -209,6 +217,8 @@ namespace osu.Game.Rulesets.Taiko.Tests IsStrong = strong }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + if (strong) rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); else diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 5e79166bec..bf1274256b 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -4,12 +4,15 @@ using System; using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Audio; namespace osu.Game.Rulesets.Taiko.UI { @@ -18,16 +21,26 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public InputDrum() + private const float middle_split = 0.025f; + + private readonly ControlPointInfo controlPoints; + + public InputDrum(ControlPointInfo controlPoints) { + this.controlPoints = controlPoints; + RelativeSizeAxes = Axes.Both; FillMode = FillMode.Fit; + } - const float middle_split = 0.025f; + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + var sampleMappings = new DrumSampleMapping(controlPoints, audio); Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, sampleMappings) { Name = "Left Half", Anchor = Anchor.Centre, @@ -38,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, sampleMappings) { Name = "Right Half", Anchor = Anchor.Centre, @@ -72,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + private readonly DrumSampleMapping sampleMappings; + + public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings) { + this.sampleMappings = sampleMappings; + Masking = true; Children = new Drawable[] @@ -128,15 +145,21 @@ namespace osu.Game.Rulesets.Taiko.UI Drawable target = null; Drawable back = null; + var drumSample = sampleMappings.SampleAt(Time.Current); + if (action == CentreAction) { target = centreHit; back = centre; + + drumSample.Centre.Play(); } else if (action == RimAction) { target = rimHit; back = rim; + + drumSample.Rim.Play(); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index ac3796f5b8..3fdbd056ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -16,6 +16,7 @@ using osu.Framework.Extensions.Color4Extensions; using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Taiko.UI { @@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Box overlayBackground; private readonly Box background; - public TaikoPlayfield() + public TaikoPlayfield(ControlPointInfo controlPoints) : base(Axes.X) { AddRangeInternal(new Drawable[] @@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI { RelativeSizeAxes = Axes.Both, }, - new InputDrum + new InputDrum(controlPoints) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -249,7 +250,9 @@ namespace osu.Game.Rulesets.Taiko.UI { topLevelHitContainer.Add(judgedObject.CreateProxy()); } - catch { } + catch + { + } } hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 48ee0a5b42..614b446181 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Taiko.UI public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); - protected override Playfield CreatePlayfield() => new TaikoPlayfield + protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs new file mode 100644 index 0000000000..d3e7958ec1 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -0,0 +1,214 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using NUnit.Framework; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Tests.Resources; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyBeatmapDecoderTest + { + [Test] + public void TestDecodeBeatmapGeneral() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.DecodeBeatmap(stream); + var beatmapInfo = beatmap.BeatmapInfo; + var metadata = beatmap.Metadata; + + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); + Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(164471, metadata.PreviewTime); + Assert.IsFalse(beatmapInfo.Countdown); + Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsFalse(beatmapInfo.LetterboxInBreaks); + Assert.IsFalse(beatmapInfo.SpecialStyle); + Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + } + } + + [Test] + public void TestDecodeBeatmapEditor() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo; + + int[] expectedBookmarks = + { + 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, + 95901, 106450, 116999, 119637, 130186, 140735, 151285, + 161834, 164471, 175020, 185570, 196119, 206669, 209306 + }; + Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + for (int i = 0; i < expectedBookmarks.Length; i++) + Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(4, beatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(2, beatmapInfo.TimelineZoom); + } + } + + [Test] + public void TestDecodeBeatmapMetadata() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.DecodeBeatmap(stream); + var beatmapInfo = beatmap.BeatmapInfo; + var metadata = beatmap.Metadata; + + Assert.AreEqual("Renatus", metadata.Title); + Assert.AreEqual("Renatus", metadata.TitleUnicode); + Assert.AreEqual("Soleily", metadata.Artist); + Assert.AreEqual("Soleily", metadata.ArtistUnicode); + Assert.AreEqual("Gamu", metadata.AuthorString); + Assert.AreEqual("Insane", beatmapInfo.Version); + Assert.AreEqual(string.Empty, metadata.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); + Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); + Assert.AreEqual(241526, metadata.OnlineBeatmapSetID); + } + } + + [Test] + public void TestDecodeBeatmapDifficulty() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty; + + Assert.AreEqual(6.5f, difficulty.DrainRate); + Assert.AreEqual(4, difficulty.CircleSize); + Assert.AreEqual(8, difficulty.OverallDifficulty); + Assert.AreEqual(9, difficulty.ApproachRate); + Assert.AreEqual(1.8f, difficulty.SliderMultiplier); + Assert.AreEqual(2, difficulty.SliderTickRate); + } + } + + [Test] + public void TestDecodeBeatmapEvents() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.DecodeBeatmap(stream); + var metadata = beatmap.Metadata; + var breakPoint = beatmap.Breaks[0]; + + Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile); + Assert.AreEqual(122474, breakPoint.StartTime); + Assert.AreEqual(140135, breakPoint.EndTime); + Assert.IsTrue(breakPoint.HasEffect); + } + } + + [Test] + public void TestDecodeBeatmapTimingPoints() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.DecodeBeatmap(stream); + var controlPoints = beatmap.ControlPointInfo; + + Assert.AreEqual(4, controlPoints.TimingPoints.Count); + var timingPoint = controlPoints.TimingPoints[0]; + Assert.AreEqual(956, timingPoint.Time); + Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + var difficultyPoint = controlPoints.DifficultyPoints[0]; + Assert.AreEqual(116999, difficultyPoint.Time); + Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); + + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + var soundPoint = controlPoints.SamplePoints[0]; + Assert.AreEqual(956, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(60, soundPoint.SampleVolume); + + Assert.AreEqual(8, controlPoints.EffectPoints.Count); + var effectPoint = controlPoints.EffectPoints[0]; + Assert.AreEqual(53703, effectPoint.Time); + Assert.IsTrue(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + } + } + + [Test] + public void TestDecodeBeatmapColors() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var comboColors = decoder.DecodeBeatmap(stream).ComboColors; + + Color4[] expectedColors = + { + new Color4(142, 199, 255, 255), + new Color4(255, 128, 128, 255), + new Color4(128, 255, 255, 255), + new Color4(128, 255, 128, 255), + new Color4(255, 187, 255, 255), + new Color4(255, 177, 140, 255), + }; + Assert.AreEqual(expectedColors.Length, comboColors.Count); + for (int i = 0; i < expectedColors.Length; i++) + Assert.AreEqual(expectedColors[i], comboColors[i]); + } + } + + [Test] + public void TestDecodeBeatmapHitObjects() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.DecodeBeatmap(stream).HitObjects; + + var curveData = hitObjects[0] as IHasCurve; + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, hitObjects[0].StartTime); + Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = hitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, hitObjects[1].StartTime); + Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs new file mode 100644 index 0000000000..839932c640 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using NUnit.Framework; +using OpenTK; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Formats; +using osu.Game.Storyboards; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyStoryboardDecoderTest + { + [Test] + public void TestDecodeStoryboardEvents() + { + var decoder = new LegacyBeatmapDecoder(); + 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); + + Assert.IsTrue(storyboard.HasDrawable); + Assert.AreEqual(4, storyboard.Layers.Count()); + + StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); + Assert.IsNotNull(background); + Assert.AreEqual(16, background.Elements.Count()); + Assert.IsTrue(background.EnabledWhenFailing); + Assert.IsTrue(background.EnabledWhenPassing); + Assert.AreEqual("Background", background.Name); + + StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); + Assert.IsNotNull(fail); + Assert.AreEqual(0, fail.Elements.Count()); + Assert.IsTrue(fail.EnabledWhenFailing); + Assert.IsFalse(fail.EnabledWhenPassing); + Assert.AreEqual("Fail", fail.Name); + + StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); + Assert.IsNotNull(pass); + Assert.AreEqual(0, pass.Elements.Count()); + Assert.IsFalse(pass.EnabledWhenFailing); + Assert.IsTrue(pass.EnabledWhenPassing); + Assert.AreEqual("Pass", pass.Name); + + StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); + Assert.IsNotNull(foreground); + Assert.AreEqual(151, foreground.Elements.Count()); + Assert.IsTrue(foreground.EnabledWhenFailing); + Assert.IsTrue(foreground.EnabledWhenPassing); + Assert.AreEqual("Foreground", foreground.Name); + + int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); + int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation)); + int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample)); + + Assert.AreEqual(15, spriteCount); + Assert.AreEqual(1, animationCount); + Assert.AreEqual(0, sampleCount); + Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); + + var sprite = background.Elements.ElementAt(0) as StoryboardSprite; + Assert.NotNull(sprite); + Assert.IsTrue(sprite.HasCommands); + Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); + Assert.IsTrue(sprite.IsDrawable); + Assert.AreEqual(Anchor.Centre, sprite.Origin); + Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); + + var animation = background.Elements.ElementAt(12) as StoryboardAnimation; + Assert.NotNull(animation); + Assert.AreEqual(141175, animation.EndTime); + Assert.AreEqual(10, animation.FrameCount); + Assert.AreEqual(30, animation.FrameDelay); + Assert.IsTrue(animation.HasCommands); + Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition); + Assert.IsTrue(animation.IsDrawable); + Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType); + Assert.AreEqual(Anchor.Centre, animation.Origin); + Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path); + Assert.AreEqual(78993, animation.StartTime); + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs new file mode 100644 index 0000000000..7143550ee2 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -0,0 +1,176 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using DeepEqual.Syntax; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Resources; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class OsuJsonDecoderTest + { + private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; + private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; + private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; + + [Test] + public void TestDecodeMetadata() + { + var beatmap = decodeAsJson(normal); + var meta = beatmap.BeatmapInfo.Metadata; + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual("Soleily", meta.Artist); + Assert.AreEqual("Soleily", meta.ArtistUnicode); + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); + Assert.AreEqual("Gamu", meta.AuthorString); + Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); + Assert.AreEqual(164471, meta.PreviewTime); + Assert.AreEqual(string.Empty, meta.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); + Assert.AreEqual("Renatus", meta.Title); + Assert.AreEqual("Renatus", meta.TitleUnicode); + } + + [Test] + public void TestDecodeGeneral() + { + var beatmap = decodeAsJson(normal); + var beatmapInfo = beatmap.BeatmapInfo; + Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(false, beatmapInfo.Countdown); + Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(false, beatmapInfo.SpecialStyle); + Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); + Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); + } + + [Test] + public void TestDecodeEditor() + { + var beatmap = decodeAsJson(normal); + var beatmapInfo = beatmap.BeatmapInfo; + + int[] expectedBookmarks = + { + 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, + 95901, 106450, 116999, 119637, 130186, 140735, 151285, + 161834, 164471, 175020, 185570, 196119, 206669, 209306 + }; + Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + for (int i = 0; i < expectedBookmarks.Length; i++) + Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(4, beatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(2, beatmapInfo.TimelineZoom); + } + + [Test] + public void TestDecodeDifficulty() + { + var beatmap = decodeAsJson(normal); + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + Assert.AreEqual(6.5f, difficulty.DrainRate); + Assert.AreEqual(4, difficulty.CircleSize); + Assert.AreEqual(8, difficulty.OverallDifficulty); + Assert.AreEqual(9, difficulty.ApproachRate); + Assert.AreEqual(1.8f, difficulty.SliderMultiplier); + Assert.AreEqual(2, difficulty.SliderTickRate); + } + + [Test] + public void TestDecodeColors() + { + var beatmap = decodeAsJson(normal); + Color4[] expected = + { + new Color4(142, 199, 255, 255), + new Color4(255, 128, 128, 255), + new Color4(128, 255, 255, 255), + new Color4(128, 255, 128, 255), + new Color4(255, 187, 255, 255), + new Color4(255, 177, 140, 255), + }; + Assert.AreEqual(expected.Length, beatmap.ComboColors.Count); + for (int i = 0; i < expected.Length; i++) + Assert.AreEqual(expected[i], beatmap.ComboColors[i]); + } + + [Test] + public void TestDecodeHitObjects() + { + var beatmap = decodeAsJson(normal); + + var curveData = beatmap.HitObjects[0] as IHasCurve; + var positionData = beatmap.HitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = beatmap.HitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); + Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + } + + [TestCase(normal)] + [TestCase(marathon)] + // Currently fails: + // [TestCase(with_sb)] + public void TestParity(string beatmap) + { + var beatmaps = decode(beatmap); + beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded); + } + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) + { + using (var stream = Resource.OpenResource(filename)) + using (var sr = new StreamReader(stream)) + { + + var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr); + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + using (var sr2 = new StreamReader(ms)) + { + sw.Write(legacyDecoded.Serialize()); + sw.Flush(); + + ms.Position = 0; + return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2)); + } + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs deleted file mode 100644 index 53989c8775..0000000000 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Beatmaps.Formats; -using osu.Game.Tests.Resources; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Tests.Beatmaps.Formats -{ - [TestFixture] - public class OsuLegacyDecoderTest - { - [Test] - public void TestDecodeMetadata() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmap = decoder.Decode(new StreamReader(stream)); - var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); - Assert.AreEqual("Soleily", meta.Artist); - Assert.AreEqual("Soleily", meta.ArtistUnicode); - Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); - Assert.AreEqual("Gamu", meta.AuthorString); - Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471, meta.PreviewTime); - Assert.AreEqual(string.Empty, meta.Source); - Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); - Assert.AreEqual("Renatus", meta.Title); - Assert.AreEqual("Renatus", meta.TitleUnicode); - } - } - - [Test] - public void TestDecodeGeneral() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmapInfo = decoder.Decode(new StreamReader(stream)).BeatmapInfo; - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); - Assert.AreEqual(false, beatmapInfo.Countdown); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); - Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); - Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); - } - } - - [Test] - public void TestDecodeEditor() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmap = decoder.Decode(new StreamReader(stream)).BeatmapInfo; - int[] expectedBookmarks = - { - 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, - 95901, 106450, 116999, 119637, 130186, 140735, 151285, - 161834, 164471, 175020, 185570, 196119, 206669, 209306 - }; - Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length); - for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]); - Assert.AreEqual(1.8, beatmap.DistanceSpacing); - Assert.AreEqual(4, beatmap.BeatDivisor); - Assert.AreEqual(4, beatmap.GridSize); - Assert.AreEqual(2, beatmap.TimelineZoom); - } - } - - [Test] - public void TestDecodeDifficulty() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmap = decoder.Decode(new StreamReader(stream)); - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; - Assert.AreEqual(6.5f, difficulty.DrainRate); - Assert.AreEqual(4, difficulty.CircleSize); - Assert.AreEqual(8, difficulty.OverallDifficulty); - Assert.AreEqual(9, difficulty.ApproachRate); - Assert.AreEqual(1.8f, difficulty.SliderMultiplier); - Assert.AreEqual(2, difficulty.SliderTickRate); - } - } - - [Test] - public void TestDecodeColors() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmap = decoder.Decode(new StreamReader(stream)); - Color4[] expected = - { - new Color4(142, 199, 255, 255), - new Color4(255, 128, 128, 255), - new Color4(128, 255, 255, 255), - new Color4(128, 255, 128, 255), - new Color4(255, 187, 255, 255), - new Color4(255, 177, 140, 255), - }; - Assert.AreEqual(expected.Length, beatmap.ComboColors.Count); - for (int i = 0; i < expected.Length; i++) - Assert.AreEqual(expected[i], beatmap.ComboColors[i]); - } - } - - [Test] - public void TestDecodeHitObjects() - { - var decoder = new OsuLegacyDecoder(); - using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - { - var beatmap = decoder.Decode(new StreamReader(stream)); - - var curveData = beatmap.HitObjects[0] as IHasCurve; - var positionData = (IHasPosition)beatmap.HitObjects[0]; - - Assert.IsNotNull(positionData); - Assert.IsNotNull(curveData); - Assert.AreEqual(new Vector2(192, 168), positionData.Position); - Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); - Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); - - positionData = (IHasPosition)beatmap.HitObjects[1]; - - Assert.IsNotNull(positionData); - Assert.AreEqual(new Vector2(304, 56), positionData.Position); - Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); - Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); - } - } - } -} diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 12bbde5b57..ffe735c89f 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 = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); diff --git a/osu.Game.Tests/Resources/Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu b/osu.Game.Tests/Resources/Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu new file mode 100644 index 0000000000..f7b33fa6a8 --- /dev/null +++ b/osu.Game.Tests/Resources/Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu @@ -0,0 +1,1997 @@ +osu file format v13 + +[General] +AudioFilename: yotsuya192.mp3 +AudioLeadIn: 0 +PreviewTime: 71220 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 0 +LetterboxInBreaks: 0 +WidescreenStoryboard: 1 + +[Editor] +Bookmarks: 24456,34275,43002,53911,71366,88820,99729,117184,143366,152093,160820,169547 +DistanceSpacing: 1.4 +BeatDivisor: 4 +GridSize: 4 +TimelineZoom: 1 + +[Metadata] +Title:Yotsuya-san ni Yoroshiku +TitleUnicode:四ツ谷さんによろしく +Artist:Himeringo +ArtistUnicode:ひめりんご +Creator:RLC +Version:Winber1's Extreme +Source: +Tags:flask nyquill winber1 skystar amamiya yuko cheesiest onosakihito utaite leave it to eight hatsune miku vocaloid +BeatmapID:378781 +BeatmapSetID:100049 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:8.5 +ApproachRate:9.5 +SliderMultiplier:1.9 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"primary.jpg",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +Sprite,Background,Centre,"SB\lyric\ja-21.png",320,240 + S,0,117175,,0.8 + F,0,117175,117584,0,1 + M,0,117175,118675,192,393,-12,393 + F,0,117584,118402,1 + F,0,118402,118675,1,0 +Sprite,Background,Centre,"SB\lyric\ja-22.png",320,240 + S,0,118675,,0.8 + F,0,118675,119084,0,1 + M,0,118675,119629,150,393,23,393 + F,0,119084,119357,1 + F,0,119357,119629,1,0 +Sprite,Background,Centre,"SB\lyric\ja-22-repeat.png",320,240 + S,0,119357,,0.8 + M,0,119629,,196,393 + F,0,119629,119902,0,1 + M,0,119629,121947,196,393,-58,393 + F,0,119902,121675,1 + F,0,121675,121947,1,0 +Sprite,Background,Centre,"SB\lyric\ja-23.png",320,240 + S,0,121947,,0.8 + F,0,121947,122357,0,1 + M,0,121947,123038,158,393,23,393 + F,0,122357,122766,1 + F,0,122766,123038,1,0 +Sprite,Background,Centre,"SB\lyric\ja-24.png",320,240 + S,0,123038,,0.8 + F,0,123038,123447,0,1 + M,0,123038,123720,249,393,187,393 + F,0,123447,123720,1 +Sprite,Background,Centre,"SB\lyric\en-21.png",320,240 + S,0,117175,,0.6 + F,0,117175,117584,0,1 + M,0,117175,117925,641,425,551,425 + F,0,117584,118402,1 + M,0,117925,118675,551,425,461,425 + F,0,118402,118675,1,0 +Sprite,Background,Centre,"SB\lyric\en-22.png",320,240 + S,0,118675,,0.6 + F,0,118675,119084,0,1 + M,0,118675,119152,616,425,554,425 + F,0,119084,119357,1 + M,0,119152,119629,554,425,489,425 + F,0,119357,119629,1,0 +Sprite,Background,Centre,"SB\lyric\en-22-repeat.png",320,240 + S,0,119629,,0.6 + F,0,119629,120038,0,1 + M,0,119629,121947,683,425,417,425 + F,0,120038,121675,1 + F,0,121675,121947,1,0 +Sprite,Background,Centre,"SB\lyric\en-23.png",320,240 + S,0,121947,,0.6 + F,0,121947,122357,0,1 + M,0,121947,123038,617,425,487,425 + F,0,122357,122766,1 + F,0,122766,123038,1,0 +Sprite,Background,Centre,"SB\lyric\en-24.png",320,240 + S,0,123038,,0.6 + F,0,123038,123447,0,1 + M,0,123038,123720,515,425,446,425 +Sprite,Background,Centre,"SB\lower mask.png",320,240 + S,0,117175,,0.625 + R,0,117175,,0.0001 + M,0,117175,123720,320,330 +Sprite,Background,Centre,"SB\black.jpg",320,240 + F,0,-97,,1 + F,0,20084,23357,1,0 + F,0,23357,24447,0 + F,0,24447,25470,0,1 + F,0,25538,26629,0,1 + F,0,27720,28811,0.5,1 + F,0,29902,30993,0,1 + F,0,32084,33175,0.5,1 + F,0,34266,,0 + F,0,70266,,1 + S,0,70266,,1.267815 + R,0,70266,,0.0001 + F,0,71357,,0 + F,0,71357,,0 + F,0,86629,,1 + F,0,87720,88538,1,0 + F,0,88538,,0 + M,0,108877,116493,314,289,312,322 + F,0,116084,,1 + F,0,117175,117720,0,0.5 + F,0,117720,118266,0.5,0 + F,0,118266,118811,0,0.5 + F,0,118811,119357,0.5,0 + F,0,119357,119902,0,0.5 + F,0,119902,120447,0.5,0 + F,0,120447,120993,0,0.5 + F,0,120993,121538,0.5,0 + F,0,121538,122084,0,0.5 + F,0,122084,122629,0.5,0 + F,0,122629,123175,0,0.5 + F,0,123175,123686,0.5,0 + F,0,123720,,1 + F,0,126175,,1,0.07840011 + F,0,141175,,1 + F,0,143357,144447,0,1 + F,0,145538,146629,0.5,1 + F,0,147720,148811,0,1 + F,0,149902,150993,0.5,1 + F,0,150993,,1 + F,0,152084,,0 + F,0,171447,,1 + F,0,171447,177720,1 +Animation,Background,Centre,"SB\red jitter\red_0000.jpg",320,240,10,30,LoopForever + S,0,78993,,1.001 + R,0,78993,,0.0001 + F,0,78993,80084,0,1 + F,0,80084,,1 + F,0,86629,,0 + F,0,133538,134629,0.9180929,1 + F,0,141175,,1 +Sprite,Background,Centre,"SB\brown.jpg",320,240 + M,0,71357,,320,240 + S,0,71357,,1.001 + R,0,71357,,0.0001 + F,0,71357,80084,1 + F,0,80084,81175,1,0 +Sprite,Background,Centre,"SB\blue.jpg",320,240 + S,0,126175,,1.001 + R,0,126175,,0.0001 + F,0,126175,134629,1 + F,0,134629,135720,1,0 +Sprite,Background,Centre,"SB\cloud2.png",320,240 + S,0,126175,,4.659974 + R,0,126175,,0.0001 + M,0,126175,135175,72,29,474,29 + F,0,133538,135175,0.4690581,0 +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +Sprite,Foreground,Centre,"SB\lyric\ja-1.png",320,240 + S,0,53902,,0.8 + F,0,53902,54993,0,1 + M,0,53902,58266,232,393,263,393 + F,0,54993,57175,1 + F,0,57175,58266,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-1.png",320,240 + S,0,53902,,0.6 + F,0,53902,54993,0,1 + M,0,53902,58266,404,425,373,425 + F,0,54993,57175,1 + F,0,57175,58266,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-2.png",320,240 + S,0,58266,,0.8 + F,0,58266,59357,0,1 + M,0,58266,62629,232,393,263,393 + F,0,59357,61538,1 + F,0,61538,,1 + F,0,61538,,1 + F,0,61538,62357,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-3.png",320,240 + S,0,62629,,0.8 + F,0,62629,63720,0,1 + M,0,62629,66447,232,393,263,393 + F,0,63720,65357,1 + F,0,65357,66447,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-4.png",320,240 + S,0,66447,,0.8 + F,0,66447,67538,0,1 + M,0,66447,70266,232,393,263,393 + F,0,67538,69175,1 + F,0,69175,70266,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-5.png",320,240 + S,0,70266,,0.5 + M,0,70266,70402,179,335 +Sprite,Foreground,Centre,"SB\lyric\ja-5.png",320,240 + S,0,70402,,0.8 + M,0,70402,70538,469,297 +Sprite,Foreground,Centre,"SB\lyric\ja-5.png",320,240 + S,0,70538,,1.1 + M,0,70538,70675,255,158 +Sprite,Foreground,Centre,"SB\lyric\ja-5RED.png",320,240 + S,0,70675,,1.3 + F,0,70675,70947,1 + M,0,70675,71220,320,240 + F,0,70947,71357,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-6-1.png",320,240 + S,0,71357,,0.8 + M,0,71357,71902,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-6-2.png",320,240 + S,0,71902,,0.8 + M,0,71902,72447,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-6-3.png",320,240 + S,0,72447,,0.8 + M,0,72447,72857,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-7.png",320,240 + S,0,72857,,0.8 + M,0,72857,73947,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-8.png",320,240 + S,0,73947,,0.8 + M,0,73947,76129,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-9.png",320,240 + S,0,76129,,0.8 + M,0,76129,77220,320,393 +Sprite,Foreground,Centre,"SB\lyric\ja-10-2.png",320,240 + S,0,78311,,0.8 + M,0,78311,80084,376,393 +Sprite,Foreground,Centre,"SB\lyric\ja-10-1.png",320,240 + S,0,77220,,0.8 + M,0,77220,80084,264,393 +Animation,Foreground,Centre,"SB\lyric\11-1\ja-11-1_0000.png",320,240,10,30,LoopForever + S,0,80084,,0.8 + M,0,80084,81584,230,326 +Animation,Foreground,Centre,"SB\lyric\11-2\ja-11-2_0000.png",320,240,10,30,LoopForever + S,0,80629,,1.1 + M,0,80629,81584,325,305 +Animation,Foreground,Centre,"SB\lyric\11-3\ja-11-3_0000.png",320,240,10,30,LoopForever + S,0,81175,,1.3 + M,0,81175,81584,425,284 +Animation,Foreground,Centre,"SB\lyric\12\ja-12_0000.png",320,240,10,30,LoopForever + S,0,81584,,1.1 + M,0,81584,82675,306,214 +Animation,Foreground,Centre,"SB\lyric\13\ja-13_0000.png",320,240,10,30,LoopForever + S,0,82675,,0.8 + M,0,82675,84857,315,294 +Animation,Foreground,Centre,"SB\lyric\14\ja-14_0000.png",320,240,10,30,LoopForever + S,0,84857,,1.1 + M,0,84857,85947,320,222 +Animation,Foreground,Centre,"SB\lyric\15-1\ja-15-1_0000.png",320,240,10,30,LoopForever + S,0,85947,,0.8 + M,0,85947,86629,269,393 +Sprite,Foreground,Centre,"SB\lyric\ja-15-1.png",320,240 + S,0,86629,,0.8 + M,0,86629,87720,269,393 + F,0,87720,88538,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-15-2.png",320,240 + S,0,87175,,0.8 + M,0,87175,87720,373,393 + F,0,87720,88538,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-16.png",320,240 + S,0,99720,,0.8 + F,0,99720,100811,0,1 + M,0,99720,104084,232,393,263,393 + F,0,100811,102993,1 + F,0,102993,104084,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-17.png",320,240 + S,0,104084,,0.8 + F,0,104084,105175,0,1 + M,0,104084,108447,232,393,263,393 + F,0,105175,107357,1 + F,0,107357,108175,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-18.png",320,240 + S,0,108447,,0.8 + F,0,108447,109538,0,1 + M,0,108447,112266,232,393,263,393 + F,0,109538,111175,1 + F,0,111175,112266,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-19.png",320,240 + S,0,112266,,0.8 + F,0,112266,113357,0,1 + M,0,112266,116084,232,393,263,393 + F,0,113357,114993,1 + F,0,114993,116084,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-20_1.png",320,240 + S,0,116084,,0.5 + M,0,116084,116220,478,319 +Sprite,Foreground,Centre,"SB\lyric\ja-20_1.png",320,240 + S,0,116220,,0.8 + M,0,116220,116357,160,257 +Sprite,Foreground,Centre,"SB\lyric\ja-20_1.png",320,240 + S,0,116357,,1.1 + M,0,116357,116493,393,161 +Sprite,Foreground,Centre,"SB\lyric\ja-20RED.png",320,240 + M,0,116493,,320,240 + S,0,116493,,1.3 + F,0,116493,116766,1 + F,0,116766,117175,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-24RED.png",320,240 + M,0,123720,,187,393 + S,0,123720,,0.8 + F,0,123720,124811,1 + F,0,124811,125902,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-25-1.png",320,240 + S,0,125766,,0.8 + M,0,125766,126175,156,312 +Sprite,Foreground,Centre,"SB\lyric\ja-25-2.png",320,240 + S,0,126175,,0.8 + M,0,126175,126993,156,312 + F,0,127538,127811,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-25-3.png",320,240 + S,0,126447,,1.1 + R,0,126447,,-0.189046 + M,0,126447,126993,306,288 + F,0,127538,,1 + F,0,127538,127811,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-25-4.png",320,240 + M,0,126993,,388,400 + S,0,126993,,1.3 + R,0,126993,,0.1785437 + F,0,127538,127811,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-26.png",320,240 + S,0,127402,,0.8 + F,0,127402,127538,0,1 + M,0,127402,128084,315,201 + R,0,127402,128084,0.1050253 + F,0,127538,128084,1 + M,0,128084,128493,315,201,315,236 + F,0,128084,128493,1,0 + R,0,128084,128493,0.1050253,0.3360815 +Sprite,Foreground,Centre,"SB\lyric\ja-27.png",320,240 + S,0,128493,,0.9 + F,0,128493,128629,0,1 + M,0,128493,130266,191,426 + R,0,128493,130266,0.06301453 + F,0,128629,130266,1 + M,0,130266,130675,191,426,191,458 + F,0,130266,130675,1,0 + R,0,130266,130675,0.06301453,0.2835687 +Sprite,Foreground,Centre,"SB\lyric\ja-28.png",320,240 + S,0,130675,,0.8 + R,0,130675,,-0.1155283 + F,0,130675,130811,0,1 + M,0,130675,131357,320,240 + F,0,130811,131357,1 + F,0,131357,131766,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-29.png",320,240 + S,0,131766,,0.8 + F,0,131766,131902,0,1 + M,0,131766,133538,230,154 + R,0,131766,133538,0.1260309 + F,0,131902,133538,1 + M,0,133538,134629,230,154,230,206 + F,0,133538,134629,1,0 + R,0,133538,134629,0.1260309,0.2835693 +Animation,Foreground,Centre,"SB\lyric\12\ja-12_0000.png",320,240,10,30,LoopForever + S,0,136129,,1.1 + M,0,136129,137220,306,214 +Animation,Foreground,Centre,"SB\lyric\13\ja-13_0000.png",320,240,10,30,LoopForever + S,0,137220,,0.8 + M,0,137220,139402,315,294 +Animation,Foreground,Centre,"SB\lyric\14\ja-14_0000.png",320,240,10,30,LoopForever + S,0,139402,,1.1 + M,0,139402,140493,320,222 +Animation,Foreground,Centre,"SB\lyric\30-1\ja-30-1_0000.png",320,240,10,30,LoopForever + S,0,134629,,0.8 + M,0,134629,136129,153,292 +Animation,Foreground,Centre,"SB\lyric\30-2\ja-30-2_0000.png",320,240,10,30,LoopForever + S,0,135175,,1.1 + M,0,135175,136129,333,216 +Animation,Foreground,Centre,"SB\lyric\30-3\ja-30-3_0000.png",320,240,10,30,LoopForever + S,0,135720,,1.3 + M,0,135720,136129,485,133 +Animation,Foreground,Centre,"SB\lyric\34-1\ja-34-1_0000.png",320,240,10,30,LoopForever + S,0,140493,,0.8 + M,0,140493,141175,269,393 +Sprite,Foreground,Centre,"SB\lyric\ja-34-1.png",320,240 + S,0,141175,,0.8 + M,0,141175,143357,269,393 + F,0,142266,143357,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-34-2.png",320,240 + S,0,141720,,0.8 + M,0,141720,143357,391,393 + F,0,142266,143357,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-2.png",320,240 + S,0,58266,,0.6 + F,0,58266,59357,0,1 + M,0,58266,62629,404,425,373,425 + F,0,59357,61538,1 + F,0,61538,62357,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-3.png",320,240 + S,0,62629,,0.6 + F,0,62629,63720,0,1 + M,0,62629,66447,404,425,373,425 + F,0,63720,65357,1 + F,0,65357,66447,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-4.png",320,240 + S,0,66447,,0.6 + F,0,66447,67538,0,1 + M,0,66447,70266,404,425,373,425 + F,0,67538,69175,1 + F,0,69175,70266,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-5.png",320,240 + F,0,70266,,1 + S,0,70266,,0.4 + M,0,70266,70402,436,192 +Sprite,Foreground,Centre,"SB\lyric\en-5.png",320,240 + S,0,70402,,0.6 + M,0,70402,70538,181,97 +Sprite,Foreground,Centre,"SB\lyric\en-5.png",320,240 + S,0,70538,,0.8 + M,0,70538,70675,391,392 +Sprite,Foreground,Centre,"SB\lyric\en-5RED.png",320,240 + M,0,70675,,320,294 + F,0,70675,70947,1 + F,0,70947,71357,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-6-1.png",320,240 + S,0,71357,,0.7885935 + M,0,71357,71902,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-6-2.png",320,240 + S,0,71902,,0.7357419 + M,0,71902,72447,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-6-3.png",320,240 + S,0,72447,,0.8084131 + M,0,72447,72857,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-7.png",320,240 + S,0,72857,,0.8018063 + M,0,72857,73947,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-8.png",320,240 + S,0,73947,,0.6961035 + M,0,73947,76129,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-9.png",320,240 + S,0,76129,,0.762168 + M,0,76129,77220,320,425 +Sprite,Foreground,Centre,"SB\lyric\en-10-1.png",320,240 + S,0,77220,,0.6564645 + M,0,77220,80084,262,425 +Sprite,Foreground,Centre,"SB\lyric\en-10-2.png",320,240 + S,0,78311,,0.5837936 + M,0,78311,80084,411,425 +Sprite,Foreground,Centre,"SB\lyric\en-16.png",320,240 + S,0,99720,,0.6 + F,0,99720,100811,0,1 + M,0,99720,104084,404,425,373,425 + F,0,100811,102993,1 + F,0,102993,104084,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-17-1.png",320,240 + S,0,104084,,0.6 + F,0,104084,105175,0,1 + M,0,104084,108447,404,425,373,425 + F,0,105175,107357,1 + F,0,107357,108175,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-17-2.png",320,240 + S,0,104084,,0.6 + F,0,104084,105175,0,1 + M,0,104084,108175,232,457,263,457 + F,0,105175,107357,1 + F,0,107357,108175,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-18.png",320,240 + S,0,108447,,0.6 + F,0,108447,109538,0,1 + M,0,108447,112266,404,425,373,425 + F,0,109538,111175,1 + F,0,111175,112266,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-19.png",320,240 + S,0,112266,,0.6 + F,0,112266,113357,0,1 + M,0,112266,116084,404,425,373,425 + F,0,113357,114993,1 + F,0,114993,116084,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-20-1.png",320,240 + S,0,116084,,0.4 + M,0,116084,116220,205,167 +Sprite,Foreground,Centre,"SB\lyric\en-20-1.png",320,240 + S,0,116220,,0.6 + M,0,116220,116357,389,252 +Sprite,Foreground,Centre,"SB\lyric\en-20-1.png",320,240 + S,0,116357,,0.8 + M,0,116357,116493,220,395 +Sprite,Foreground,Centre,"SB\lyric\en-20RED.png",320,240 + M,0,116493,,320,293 + F,0,116493,116766,1 + F,0,116766,117175,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-24RED.png",320,240 + M,0,123720,,446,425 + S,0,123720,,0.6 + F,0,123720,124811,1 + F,0,124811,125902,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-25-1.png",320,240 + S,0,125766,,0.6 + M,0,125766,126175,408,240 +Sprite,Foreground,Centre,"SB\lyric\en-25-2.png",320,240 + S,0,126175,,0.6 + M,0,126175,126993,408,240 + F,0,127538,127811,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-25-3.png",320,240 + M,0,126447,,525,277 + S,0,126447,,0.8 + R,0,126447,,0.189046 + F,0,127538,127811,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-25-4.png",320,240 + M,0,126993,,219,400 + S,0,126993,,1 + R,0,126993,,-0.1260309 + F,0,126993,127402,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-26.png",320,240 + S,0,127402,,0.7 + F,0,127402,127538,0,1 + M,0,127402,128084,416,325 + R,0,127402,128084,-0.1680408 + F,0,127538,128084,1 + M,0,128084,128493,416,325,416,391 + F,0,128084,128493,1,0 + R,0,128084,128493,-0.1680408,-0.3990967 +Sprite,Foreground,Centre,"SB\lyric\en-27.png",320,240 + S,0,128493,,0.6 + F,0,128493,128629,0,1 + M,0,128493,130266,443,226 + R,0,128493,130266,-0.08402039 + F,0,128629,130266,1 + M,0,130266,130675,443,226,443,252 + F,0,130266,130675,1,0 + R,0,130266,130675,-0.08402039,-0.2310565 +Sprite,Foreground,Centre,"SB\lyric\en-28.png",320,240 + S,0,130675,,0.7 + R,0,130675,,0.273067 + F,0,130675,130811,0,1 + M,0,130675,131357,140,303 + F,0,130811,131357,1 + F,0,131357,131766,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-29.png",320,240 + S,0,131766,,0.7 + F,0,131766,131902,0,1 + M,0,131766,133538,438,257 + R,0,131766,133538,-0.1890472 + F,0,131902,133538,1 + M,0,133538,134629,438,257,438,314 + F,0,133538,134629,1,0 + R,0,133538,134629,-0.1890472,-0.4306067 +Animation,Foreground,Centre,"SB\lyric\11-1\en-11-1_0000.png",320,240,10,30,LoopForever + S,0,80357,,0.6 + M,0,80357,81584,336,384 +Animation,Foreground,Centre,"SB\lyric\11-2\en-11-2_0000.png",320,240,10,30,LoopForever + S,0,80902,,0.8 + M,0,80902,81993,142,245 +Animation,Foreground,Centre,"SB\lyric\11-3\en-11-3_0000.png",320,240,10,30,LoopForever + S,0,81447,,1 + M,0,81447,81993,413,164 +Animation,Foreground,Centre,"SB\lyric\12\en-12_0000.png",320,240,10,30,LoopForever + S,0,81993,,0.8 + M,0,81993,83357,255,360 +Animation,Foreground,Centre,"SB\lyric\13\en-13_0000.png",320,240,10,30,LoopForever + S,0,83357,,0.6 + M,0,83357,85538,222,140 +Animation,Foreground,Centre,"SB\lyric\14\en-14_0000.png",320,240,10,30,LoopForever + S,0,85538,,1 + M,0,85538,86357,437,300 +Animation,Foreground,Centre,"SB\lyric\15-1\en-15-1_0000.png",320,240,10,30,LoopForever + F,0,86357,,1 + S,0,86357,,0.6564642 + M,0,86357,86629,237,430 +Sprite,Foreground,Centre,"SB\lyric\en-15-1.png",320,240 + S,0,86629,,0.6630707 + M,0,86629,87720,234,430 + F,0,86629,87720,1 + F,0,87720,88538,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-15-2.png",320,240 + S,0,87175,,0.7423482 + M,0,87175,87720,417,430 + F,0,87175,87720,1 + F,0,87720,88538,1,0 +Animation,Foreground,Centre,"SB\lyric\30-1\en-30-1_0000.png",320,240,10,30,LoopForever + F,0,134902,,1 + S,0,134902,,0.6 + M,0,134902,136129,419,374 +Animation,Foreground,Centre,"SB\lyric\30-2\en-30-2_0000.png",320,240,10,30,LoopForever + S,0,135447,,0.6 + M,0,135447,136811,156,164 +Animation,Foreground,Centre,"SB\lyric\30-3\en-30-3_0000.png",320,240,10,30,LoopForever + S,0,135993,,0.6 + M,0,135993,136811,128,404 +Animation,Foreground,Centre,"SB\lyric\12\en-12_0000.png",320,240,10,30,LoopForever + S,0,136811,,0.8 + M,0,136811,137902,439,391 +Animation,Foreground,Centre,"SB\lyric\13\en-13_0000.png",320,240,10,30,LoopForever + S,0,137902,,0.6 + M,0,137902,140084,450,142 +Animation,Foreground,Centre,"SB\lyric\14\en-14_0000.png",320,240,10,30,LoopForever + S,0,140084,,1 + M,0,140084,140902,191,294 +Animation,Foreground,Centre,"SB\lyric\34-1\en-34-1_0000.png",320,240,10,30,LoopForever + S,0,140902,,0.6 + M,0,140902,141175,286,425 +Sprite,Foreground,Centre,"SB\lyric\en-34-1-1.png",320,240 + S,0,141175,,0.6 + M,0,141175,142266,286,425 + F,0,142266,143357,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-34-1-2.png",320,240 + S,0,141175,,0.6 + M,0,141175,142266,286,425 + F,0,142266,143357,1,0 +Sprite,Foreground,Centre,"SB\lyric\en-34-2.png",320,240 + S,0,141720,,0.6 + M,0,141720,142266,451,425 + F,0,142266,143357,1,0 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-1.png",320,240 + S,0,25538,,0.6 + M,0,25538,27720,50,240 + M,0,27720,27857,50,240,50,247 + F,0,27720,28538,1,0 + R,0,27720,28538,0,0.3567487 + M,0,27857,27993,50,247,50,263 + M,0,27993,28129,50,263,50,290 + M,0,28129,28266,50,290,50,321 + M,0,28266,28402,50,321,50,360 + M,0,28402,28538,50,360,50,408 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-2.png",320,240 + S,0,25538,,0.6 + M,0,25538,28947,104,240 + M,0,28947,29084,104,240,104,248 + F,0,28947,29766,1,0 + R,0,28947,29766,0,-0.4095996 + M,0,29084,29220,104,248,104,263 + M,0,29220,29357,104,263,104,287 + M,0,29357,29493,104,287,104,320 + M,0,29493,29629,104,320,104,360 + M,0,29629,29766,104,360,104,409 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-3.png",320,240 + S,0,25538,,0.6 + M,0,25538,28129,158,240 + M,0,28129,28266,158,240,158,249 + F,0,28129,28947,1,0 + R,0,28129,28947,0,-0.2114062 + M,0,28266,28402,158,249,158,263 + M,0,28402,28538,158,263,158,288 + M,0,28538,28675,158,288,158,321 + M,0,28675,28811,158,321,158,360 + M,0,28811,28947,158,360,158,409 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-4.png",320,240 + S,0,25538,,0.6 + M,0,25538,28811,212,240 + M,0,28811,28947,212,240,212,248 + F,0,28811,29629,1,0 + R,0,28811,29629,0,0.4624512 + M,0,28947,29084,212,248,212,263 + M,0,29084,29220,212,263,212,288 + M,0,29220,29357,212,288,212,320 + M,0,29357,29493,212,320,212,360 + M,0,29493,29629,212,360,212,407 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-5.png",320,240 + S,0,25538,,0.6 + M,0,25538,28538,266,240 + M,0,28538,28675,266,240,266,248 + F,0,28538,29357,1,0 + R,0,28538,29357,0,0.2906836 + M,0,28675,28811,266,248,266,264 + M,0,28811,28947,266,264,266,288 + M,0,28947,29084,266,288,266,321 + M,0,29084,29220,266,321,266,358 + M,0,29220,29357,266,358,266,407 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-6.png",320,240 + S,0,25538,,0.6 + M,0,25538,27993,320,240 + M,0,27993,28129,320,240,320,247 + F,0,27993,28811,1,0 + R,0,27993,28811,0,0.2906836 + M,0,28129,28266,320,247,320,263 + M,0,28266,28402,320,263,320,288 + M,0,28402,28538,320,288,320,321 + M,0,28538,28675,320,321,320,360 + M,0,28675,28811,320,360,320,408 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-7.png",320,240 + S,0,25538,,0.6 + M,0,25538,28266,374,240 + M,0,28266,28402,374,240,374,248 + F,0,28266,29084,1,0 + R,0,28266,29084,0,0.1717682 + M,0,28402,28538,374,248,374,264 + M,0,28538,28675,374,264,374,288 + M,0,28675,28811,374,288,374,320 + M,0,28811,28947,374,320,374,359 + M,0,28947,29084,374,359,374,409 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-8.png",320,240 + S,0,25538,,0.6 + M,0,25538,27857,428,240 + M,0,27857,27993,428,240,428,249 + F,0,27857,28675,1,0 + R,0,27857,28675,0,-0.237832 + M,0,27993,28129,428,249,428,263 + M,0,28129,28266,428,263,428,288 + M,0,28266,28402,428,288,428,320 + M,0,28402,28538,428,320,428,358 + M,0,28538,28675,428,358,428,408 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-9.png",320,240 + S,0,25538,,0.6 + M,0,25538,28675,482,240 + M,0,28675,28811,482,240,482,247 + F,0,28675,29493,1,0 + R,0,28675,29493,0,-0.5020905 + M,0,28811,28947,482,247,482,263 + M,0,28947,29084,482,263,482,287 + M,0,29084,29220,482,287,482,320 + M,0,29220,29357,482,320,482,360 + M,0,29357,29493,482,360,482,407 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-10-1.png",320,240 + S,0,25538,,0.6 + M,0,25538,28402,536,240 + M,0,28402,28538,536,240,536,247 + F,0,28402,29220,1,0 + R,0,28402,29220,0,-0.3038965 + M,0,28538,28675,536,247,536,263 + M,0,28675,28811,536,263,536,289 + M,0,28811,28947,536,289,536,321 + M,0,28947,29084,536,321,536,360 + M,0,29084,29220,536,360,536,407 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-ja-11.png",320,240 + S,0,25538,,0.6 + M,0,25538,29084,590,240 + F,0,28947,29902,1,0 + R,0,28947,29902,0,0.6474323 + M,0,29084,29220,590,240,590,247 + M,0,29220,29357,590,247,590,263 + M,0,29357,29493,590,263,590,288 + M,0,29493,29629,590,288,590,320 + M,0,29629,29766,590,320,590,361 + M,0,29766,29902,590,361,590,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-1.png",320,240 + S,0,29902,,0.6 + M,0,29902,32084,50,240 + M,0,32084,32221,50,240,50,247 + F,0,32084,32902,1,0 + R,0,32084,32902,0,0.4 + M,0,32221,32357,50,247,50,263 + M,0,32357,32493,50,263,50,290 + M,0,32493,32630,50,290,50,321 + M,0,32630,32766,50,321,50,360 + M,0,32766,32902,50,360,50,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-2.png",320,240 + S,0,29902,,0.6 + M,0,29902,33311,104,240 + M,0,33311,33448,104,240,104,248 + F,0,33311,34130,1,0 + R,0,33311,34130,0,-0.4 + M,0,33448,33584,104,248,104,263 + M,0,33584,33721,104,263,104,287 + M,0,33721,33857,104,287,104,320 + M,0,33857,33993,104,320,104,360 + M,0,33993,34130,104,360,104,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-3.png",320,240 + S,0,29902,,0.6 + M,0,29902,32493,158,240 + M,0,32493,32630,158,240,158,249 + F,0,32493,33311,1,0 + R,0,32493,33311,0,-0.4 + M,0,32630,32766,158,249,158,263 + M,0,32766,32902,158,263,158,288 + M,0,32902,33039,158,288,158,321 + M,0,33039,33175,158,321,158,360 + M,0,33175,33311,158,360,158,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-4.png",320,240 + S,0,29902,,0.6 + M,0,29902,33175,212,240 + M,0,33175,33311,212,240,212,248 + F,0,33175,33993,1,0 + R,0,33175,33993,0,0.4 + M,0,33311,33448,212,248,212,263 + M,0,33448,33584,212,263,212,288 + M,0,33584,33721,212,288,212,320 + M,0,33721,33857,212,320,212,360 + M,0,33857,33993,212,360,212,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-5.png",320,240 + S,0,29902,,0.6 + M,0,29902,32902,266,240 + M,0,32902,33039,266,240,266,248 + F,0,32902,33721,1,0 + R,0,32902,33721,0,0.4 + M,0,33039,33175,266,248,266,264 + M,0,33175,33311,266,264,266,288 + M,0,33311,33448,266,288,266,321 + M,0,33448,33584,266,321,266,358 + M,0,33584,33721,266,358,266,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-6.png",320,240 + S,0,29902,,0.6 + M,0,29902,32357,320,240 + M,0,32357,32493,320,240,320,247 + F,0,32357,33175,1,0 + R,0,32357,33175,0,0.4 + M,0,32493,32630,320,247,320,263 + M,0,32630,32766,320,263,320,288 + M,0,32766,32902,320,288,320,321 + M,0,32902,33039,320,321,320,360 + M,0,33039,33175,320,360,320,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-7.png",320,240 + S,0,29902,,0.6 + M,0,29902,32630,374,240 + M,0,32630,32766,374,240,374,248 + F,0,32630,33448,1,0 + R,0,32630,33448,0,0.4 + M,0,32766,32902,374,248,374,264 + M,0,32902,33039,374,264,374,288 + M,0,33039,33175,374,288,374,320 + M,0,33175,33311,374,320,374,359 + M,0,33311,33448,374,359,374,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-8.png",320,240 + S,0,29902,,0.6 + M,0,29902,32221,428,240 + M,0,32221,32357,428,240,428,249 + F,0,32221,33039,1,0 + R,0,32221,33039,0,-0.4 + M,0,32357,32493,428,249,428,263 + M,0,32493,32630,428,263,428,288 + M,0,32630,32766,428,288,428,320 + M,0,32766,32902,428,320,428,358 + M,0,32902,33039,428,358,428,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-9.png",320,240 + S,0,29902,,0.6 + M,0,29902,33039,482,240 + M,0,33039,33175,482,240,482,247 + F,0,33039,33857,1,0 + R,0,33039,33857,0,-0.4 + M,0,33175,33311,482,247,482,263 + M,0,33311,33448,482,263,482,287 + M,0,33448,33584,482,287,482,320 + M,0,33584,33721,482,320,482,360 + M,0,33721,33857,482,360,482,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-10-1.png",320,240 + S,0,29902,,0.6 + M,0,29902,32766,536,240 + M,0,32766,32902,536,240,536,247 + F,0,32766,33584,1,0 + R,0,32766,33584,0,-0.4 + M,0,32902,33039,536,247,536,263 + M,0,33039,33175,536,263,536,289 + M,0,33175,33311,536,289,536,321 + M,0,33311,33448,536,321,536,360 + M,0,33448,33584,536,360,536,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-11.png",320,240 + S,0,29902,,0.6 + M,0,29902,33448,590,240 + F,0,33311,34266,1,0 + R,0,33311,34266,0,0.4 + M,0,33448,33584,590,240,590,247 + M,0,33584,33721,590,247,590,263 + M,0,33721,33857,590,263,590,288 + M,0,33857,33993,590,288,590,320 + M,0,33993,34130,590,320,590,361 + M,0,34130,34266,590,361,590,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-1.png",320,240 + S,0,143357,,0.6 + M,0,143357,145539,50,240 + M,0,145539,145676,50,240,50,247 + F,0,145539,146357,1,0 + R,0,145539,146357,0,0.4 + M,0,145676,145812,50,247,50,263 + M,0,145812,145948,50,263,50,290 + M,0,145948,146085,50,290,50,321 + M,0,146085,146221,50,321,50,360 + M,0,146221,146357,50,360,50,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-2.png",320,240 + S,0,143357,,0.6 + M,0,143357,146766,104,240 + M,0,146766,146903,104,240,104,248 + F,0,146766,147585,1,0 + R,0,146766,147585,0,-0.4 + M,0,146903,147039,104,248,104,263 + M,0,147039,147176,104,263,104,287 + M,0,147176,147312,104,287,104,320 + M,0,147312,147448,104,320,104,360 + M,0,147448,147585,104,360,104,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-3.png",320,240 + S,0,143357,,0.6 + M,0,143357,145948,158,240 + M,0,145948,146085,158,240,158,249 + F,0,145948,146766,1,0 + R,0,145948,146766,0,-0.4 + M,0,146085,146221,158,249,158,263 + M,0,146221,146357,158,263,158,288 + M,0,146357,146494,158,288,158,321 + M,0,146494,146630,158,321,158,360 + M,0,146630,146766,158,360,158,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-4.png",320,240 + S,0,143357,,0.6 + M,0,143357,146630,212,240 + M,0,146630,146766,212,240,212,248 + F,0,146630,147448,1,0 + R,0,146630,147448,0,0.4 + M,0,146766,146903,212,248,212,263 + M,0,146903,147039,212,263,212,288 + M,0,147039,147176,212,288,212,320 + M,0,147176,147312,212,320,212,360 + M,0,147312,147448,212,360,212,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-5.png",320,240 + S,0,143357,,0.6 + M,0,143357,146357,266,240 + M,0,146357,146494,266,240,266,248 + F,0,146357,147176,1,0 + R,0,146357,147176,0,0.4 + M,0,146494,146630,266,248,266,264 + M,0,146630,146766,266,264,266,288 + M,0,146766,146903,266,288,266,321 + M,0,146903,147039,266,321,266,358 + M,0,147039,147176,266,358,266,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-6.png",320,240 + S,0,143357,,0.6 + M,0,143357,145812,320,240 + M,0,145812,145948,320,240,320,247 + F,0,145812,146630,1,0 + R,0,145812,146630,0,0.4 + M,0,145948,146085,320,247,320,263 + M,0,146085,146221,320,263,320,288 + M,0,146221,146357,320,288,320,321 + M,0,146357,146494,320,321,320,360 + M,0,146494,146630,320,360,320,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-7.png",320,240 + S,0,143357,,0.6 + M,0,143357,146085,374,240 + M,0,146085,146221,374,240,374,248 + F,0,146085,146903,1,0 + R,0,146085,146903,0,0.4 + M,0,146221,146357,374,248,374,264 + M,0,146357,146494,374,264,374,288 + M,0,146494,146630,374,288,374,320 + M,0,146630,146766,374,320,374,359 + M,0,146766,146903,374,359,374,409 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-8.png",320,240 + S,0,143357,,0.6 + M,0,143357,145676,428,240 + M,0,145676,145812,428,240,428,249 + F,0,145676,146494,1,0 + R,0,145676,146494,0,-0.4 + M,0,145812,145948,428,249,428,263 + M,0,145948,146085,428,263,428,288 + M,0,146085,146221,428,288,428,320 + M,0,146221,146357,428,320,428,358 + M,0,146357,146494,428,358,428,408 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-9.png",320,240 + S,0,143357,,0.6 + M,0,143357,146494,482,240 + M,0,146494,146630,482,240,482,247 + F,0,146494,147312,1,0 + R,0,146494,147312,0,-0.4 + M,0,146630,146766,482,247,482,263 + M,0,146766,146903,482,263,482,287 + M,0,146903,147039,482,287,482,320 + M,0,147039,147176,482,320,482,360 + M,0,147176,147312,482,360,482,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-10-1.png",320,240 + S,0,143357,,0.6 + M,0,143357,146221,536,240 + M,0,146221,146357,536,240,536,247 + F,0,146221,147039,1,0 + R,0,146221,147039,0,-0.4 + M,0,146357,146494,536,247,536,263 + M,0,146494,146630,536,263,536,289 + M,0,146630,146766,536,289,536,321 + M,0,146766,146903,536,321,536,360 + M,0,146903,147039,536,360,536,407 +Sprite,Foreground,Centre,"SB\lyric\y2\introSECOND-ja-11.png",320,240 + S,0,143357,,0.6 + M,0,143357,146903,590,240 + F,0,146766,147721,1,0 + R,0,146766,147721,0,0.4 + M,0,146903,147039,590,240,590,247 + M,0,147039,147176,590,247,590,263 + M,0,147176,147312,590,263,590,288 + M,0,147312,147448,590,288,590,320 + M,0,147448,147585,590,320,590,361 + M,0,147585,147721,590,361,590,408 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-1.png",320,240 + S,0,147720,,0.6 + M,0,147720,150993,104,240 + M,0,150993,151130,104,240,104,248 + F,0,150993,151812,1,0 + R,0,150993,151812,0,-0.4 + M,0,151130,151266,104,248,104,263 + M,0,151266,151403,104,263,104,287 + M,0,151403,151539,104,287,104,320 + M,0,151539,151675,104,320,104,360 + M,0,151675,151812,104,360,104,409 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-2.png",320,240 + S,0,147720,,0.6 + M,0,147720,150175,158,240 + M,0,150175,150312,158,240,158,249 + F,0,150175,150993,1,0 + R,0,150175,150993,0,-0.4 + M,0,150312,150448,158,249,158,263 + M,0,150448,150584,158,263,158,288 + M,0,150584,150721,158,288,158,321 + M,0,150721,150857,158,321,158,360 + M,0,150857,150993,158,360,158,409 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-3.png",320,240 + S,0,147720,,0.6 + M,0,147720,150857,212,240 + M,0,150857,150993,212,240,212,248 + F,0,150857,151675,1,0 + R,0,150857,151675,0,0.4 + M,0,150993,151130,212,248,212,263 + M,0,151130,151266,212,263,212,288 + M,0,151266,151403,212,288,212,320 + M,0,151403,151539,212,320,212,360 + M,0,151539,151675,212,360,212,407 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-4.png",320,240 + S,0,147720,,0.6 + M,0,147720,150584,266,240 + M,0,150584,150721,266,240,266,248 + F,0,150584,151403,1,0 + R,0,150584,151403,0,0.4 + M,0,150721,150857,266,248,266,264 + M,0,150857,150993,266,264,266,288 + M,0,150993,151130,266,288,266,321 + M,0,151130,151266,266,321,266,358 + M,0,151266,151403,266,358,266,407 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-5.png",320,240 + S,0,147720,,0.6 + M,0,147720,150039,320,240 + M,0,150039,150175,320,240,320,247 + F,0,150039,150857,1,0 + R,0,150039,150857,0,0.4 + M,0,150175,150312,320,247,320,263 + M,0,150312,150448,320,263,320,288 + M,0,150448,150584,320,288,320,321 + M,0,150584,150721,320,321,320,360 + M,0,150721,150857,320,360,320,408 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-6.png",320,240 + S,0,147720,,0.6 + M,0,147720,150312,374,240 + M,0,150312,150448,374,240,374,248 + F,0,150312,151130,1,0 + R,0,150312,151130,0,0.4 + M,0,150448,150584,374,248,374,264 + M,0,150584,150721,374,264,374,288 + M,0,150721,150857,374,288,374,320 + M,0,150857,150993,374,320,374,359 + M,0,150993,151130,374,359,374,409 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-7.png",320,240 + S,0,147720,,0.6 + M,0,147720,149903,428,240 + M,0,149903,150039,428,240,428,249 + F,0,149903,150721,1,0 + R,0,149903,150721,0,-0.4 + M,0,150039,150175,428,249,428,263 + M,0,150175,150312,428,263,428,288 + M,0,150312,150448,428,288,428,320 + M,0,150448,150584,428,320,428,358 + M,0,150584,150721,428,358,428,408 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-8.png",320,240 + S,0,147720,,0.6 + M,0,147720,150721,482,240 + M,0,150721,150857,482,240,482,247 + F,0,150721,151539,1,0 + R,0,150721,151539,0,-0.4 + M,0,150857,150993,482,247,482,263 + M,0,150993,151130,482,263,482,287 + M,0,151130,151266,482,287,482,320 + M,0,151266,151403,482,320,482,360 + M,0,151403,151539,482,360,482,407 +Sprite,Foreground,Centre,"SB\lyric\z\outroSECOND-ja-9.png",320,240 + S,0,147720,,0.6 + M,0,147720,150448,536,240 + M,0,150448,150584,536,240,536,247 + F,0,150448,151266,1,0 + R,0,150448,151266,0,-0.4 + M,0,150584,150721,536,247,536,263 + M,0,150721,150857,536,263,536,289 + M,0,150857,150993,536,289,536,321 + M,0,150993,151130,536,321,536,360 + M,0,151130,151266,536,360,536,407 +Sprite,Foreground,Centre,"SB\lyric\y1\intro-en1.png",320,240 + S,0,25538,,0.8 + M,0,25538,27720,320,280 + F,0,27720,27993,1,0 +Sprite,Foreground,Centre,"SB\lyric\y2\intro-en2.png",320,240 + M,0,29902,,320,280 + S,0,29902,,0.8 + F,0,32084,32357,1,0 +Sprite,Foreground,Centre,"SB\lyric\z\outro-en1.png",320,240 + M,0,143357,,320,280 + S,0,143357,,0.8 + F,0,145538,145811,1,0 +Sprite,Foreground,Centre,"SB\lyric\z\outro-en2.png",320,240 + M,0,147720,,320,280 + S,0,147720,,0.8 + F,0,149902,150175,1,0 +Sprite,Foreground,Centre,"SB\lyric\ja-34-1-1.png",320,240 + S,0,141174,,0.8 + M,0,141174,143356,343,393 + F,0,142265,143356,1,0 +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +2629,272.727272727273,4,2,1,30,1,0 +20083,291.26213592233,4,2,1,30,1,0 +21248,288.461538461538,4,2,1,30,1,0 +22329,-181.818181818182,4,2,1,30,0,0 +22473,-181.818181818182,4,2,1,5,0,0 +23356,272.727272727273,4,2,1,5,1,0 +23492,-100,4,2,1,10,0,0 +23628,-100,4,2,1,15,0,0 +23765,-100,4,2,1,20,0,0 +23901,-100,4,2,1,25,0,0 +24037,-100,4,2,1,30,0,0 +24174,-100,4,2,1,35,0,0 +24310,-100,4,2,1,40,0,0 +24446,-117.647058823529,4,1,1,50,0,0 +25333,-100,4,1,1,5,0,0 +25537,-100,4,1,1,50,0,0 +25810,-117.647058823529,4,2,1,50,0,0 +26492,-117.647058823529,4,1,1,50,0,0 +26765,-117.647058823529,4,2,1,50,0,0 +27719,-117.647058823529,4,1,1,50,0,0 +27992,-117.647058823529,4,2,1,50,0,0 +28674,-117.647058823529,4,1,1,50,0,0 +28878,-117.647058823529,4,2,1,5,0,0 +28946,-117.647058823529,4,2,1,50,0,0 +29901,-100,4,1,1,50,0,0 +30174,-117.647058823529,4,2,1,50,0,0 +30856,-117.647058823529,4,1,1,50,0,0 +31128,-117.647058823529,4,2,1,50,0,0 +32083,-117.647058823529,4,1,1,50,0,0 +32356,-117.647058823529,4,2,1,50,0,0 +33242,-117.647058823529,4,1,1,5,0,0 +33310,-117.647058823529,4,1,1,30,0,0 +34265,-100,4,1,1,60,0,0 +42583,-117.647058823529,4,1,1,40,0,0 +42992,-100,4,1,1,60,0,0 +51719,-133.333333333333,4,2,1,60,0,0 +53492,-117.647058823529,4,2,1,80,0,0 +53628,-200,4,2,1,60,0,0 +56015,-200,4,2,1,5,0,0 +56083,-200,4,2,1,60,0,0 +60992,-200,4,2,1,60,0,0 +61060,-200,4,1,1,20,0,0 +61128,-200,4,1,1,60,0,0 +61196,-200,4,1,1,20,0,0 +61265,-200,4,1,1,60,0,0 +61401,-200,4,1,1,20,0,0 +61537,-200,4,1,1,60,0,0 +61605,-200,4,1,1,20,0,0 +61946,-200,4,1,1,60,0,0 +62015,-200,4,1,1,20,0,0 +62355,-200,4,1,1,60,0,0 +62628,-200,4,2,1,60,0,0 +70265,-100,4,1,1,60,0,0 +71356,-90.9090909090909,4,1,1,60,0,1 +78992,-90.9090909090909,4,1,1,40,0,0 +80083,-90.9090909090909,4,1,1,60,0,1 +86628,-117.647058823529,4,1,1,60,0,0 +87719,-117.647058823529,4,1,1,60,0,0 +88810,-100,4,1,1,60,0,0 +97537,-133.333333333333,4,2,1,60,0,0 +99310,-100,4,2,1,80,0,0 +99446,-200,4,2,1,60,0,0 +101833,-200,4,2,1,5,0,0 +101901,-200,4,2,1,60,0,0 +106810,-200,4,2,1,60,0,0 +106878,-200,4,2,1,20,0,0 +106878,-200,4,2,1,60,0,0 +106946,-200,4,1,1,60,0,0 +107015,-200,4,1,1,20,0,0 +107083,-200,4,1,1,60,0,0 +107219,-200,4,1,1,20,0,0 +107355,-200,4,1,1,60,0,0 +107424,-200,4,1,1,20,0,0 +107765,-200,4,1,1,60,0,0 +107833,-200,4,1,1,20,0,0 +108174,-200,4,1,1,60,0,0 +108446,-200,4,2,1,60,0,0 +116083,-200,4,1,1,60,0,0 +117174,-133.333333333333,4,1,1,60,0,1 +123719,-200,4,1,1,20,0,0 +124674,-200,4,1,1,60,0,0 +126174,-90.9090909090909,4,1,1,60,0,1 +127810,-200,4,1,1,60,0,0 +128356,-100,4,1,1,60,0,1 +129992,-200,4,2,1,60,0,0 +130265,-100,4,1,1,60,0,1 +133537,-100,4,1,1,60,0,0 +134628,-100,4,1,1,60,0,1 +141174,-100,4,1,1,60,0,0 +142265,-117.647058823529,4,1,1,60,0,1 +143287,-117.647058823529,4,1,1,5,0,1 +143355,-117.647058823529,4,1,1,5,0,0 +143356,-133.333333333333,4,2,1,60,0,0 +147651,-133.333333333333,4,2,1,5,0,0 +147719,-133.333333333333,4,2,1,60,0,0 +152083,-100,4,1,1,60,0,0 +160810,-83.3333333333333,4,1,1,60,0,1 +162992,-76.9230769230769,4,1,1,60,0,1 +165174,-71.4285714285714,4,1,1,60,0,1 +167356,-117.647058823529,4,1,1,60,0,0 +168446,-117.647058823529,4,1,1,60,0,1 +169537,-117.647058823529,4,2,1,60,0,0 +171310,-117.647058823529,4,2,1,80,0,0 +171446,-117.647058823529,4,2,1,60,0,0 + + +[Colours] +Combo1 : 255,255,255 +Combo2 : 72,255,72 +Combo3 : 255,128,0 +Combo4 : 255,32,32 +Combo5 : 0,128,255 +Combo6 : 255,85,255 +Combo7 : 0,183,0 +Combo8 : 176,112,77 + +[HitObjects] +154,247,22401,6,0,P|56:170|169:171,1,313.500009567261 +169,170,23356,53,0,1:0:0:0: +192,92,23492,1,0,1:0:0:0: +252,40,23629,1,0,1:0:0:0: +332,28,23765,1,0,1:0:0:0: +404,60,23901,1,0,1:0:0:0: +452,124,24038,1,0,1:0:0:0: +456,204,24174,1,0,1:0:0:0: +424,276,24310,1,0,1:0:0:0: +356,316,24447,54,0,P|316:312|272:312,1,80.750001540184,0|0,0:0|2:0,0:0:0:0: +275,311,24651,1,0,2:0:0:0: +275,311,24719,1,2,2:0:0:0: +168,344,24856,1,4,3:0:0:0: +92,264,24992,1,0,0:0:0:0: +96,249,25060,1,0,2:0:0:0: +99,235,25129,1,0,2:0:0:0: +103,221,25197,1,0,2:0:0:0: +106,207,25265,54,0,P|72:144|92:104,1,121.125002310276 +108,208,25538,54,0,P|153:206|208:204,1,95 +260,88,25810,2,0,L|176:84,1,80.750001540184 +304,220,26083,1,2,0:0:0:0: +400,212,26219,1,0,0:0:0:0: +344,132,26356,1,0,0:0:0:0: +356,300,26492,54,0,P|300:360|212:348,1,161.500003080368,0|0,3:0|0:0,0:0:0:0: +200,216,26901,1,0,0:0:0:0: +200,216,27038,1,0,0:0:0:0: +108,328,27174,1,2,0:0:0:0: +108,328,27310,1,0,0:0:0:0: +28,208,27447,1,2,0:0:0:0: +112,244,27583,1,4,3:0:0:0: +112,244,27651,1,0,0:0:0:0: +112,244,27719,54,0,B|104:176|88:116|88:116|104:124|120:128,1,161.500003080368 +208,164,28129,2,0,P|248:159|284:136,1,80.750001540184,0|2,0:0|0:0,0:0:0:0: +212,72,28401,1,0,0:0:0:0: +280,240,28538,1,0,0:0:0:0: +388,140,28674,54,0,P|392:200|372:264,1,121.125002310276,0|0,3:0|0:0,0:0:0:0: +375,257,28947,2,0,P|336:252|288:252,1,80.750001540184,0|2,0:0|0:0,0:0:0:0: +456,308,29219,1,0,0:0:0:0: +376,372,29356,1,2,0:0:0:0: +376,372,29492,1,0,0:0:0:0: +376,372,29629,1,2,0:0:0:0: +376,372,29765,1,4,3:0:0:0: +376,372,29901,54,0,P|331:371|280:368,1,95 +224,248,30174,2,0,P|264:247|312:244,1,80.750001540184 +164,352,30447,1,2,0:0:0:0: +68,360,30583,1,0,0:0:0:0: +108,272,30719,1,0,0:0:0:0: +40,156,30856,54,0,P|44:80|120:36,1,161.500003080368,0|0,3:0|0:0,0:0:0:0: +136,132,31265,1,0,0:0:0:0: +136,132,31401,1,0,0:0:0:0: +256,48,31538,1,2,0:0:0:0: +256,48,31674,1,0,0:0:0:0: +376,132,31810,1,2,0:0:0:0: +312,224,31947,1,4,3:0:0:0: +312,224,32015,1,0,0:0:0:0: +312,224,32083,54,0,B|272:288|312:352|312:352|280:344,1,161.500003080368 +196,324,32492,1,0,0:0:0:0: +312,352,32629,2,0,P|348:340|396:340,1,80.750001540184,2|0,0:0|0:0,0:0:0:0: +484,368,32901,1,0,0:0:0:0: +448,229,33038,54,0,B|430:168|430:168|448:149|456:113,1,121.125002310276,2|0,0:0|0:0,0:0:0:0: +455,116,33310,2,0,L|375:132,1,80.750001540184 +208,44,33583,2,0,L|290:58,1,80.750001540184,2|0,2:0|0:0,0:0:0:0: +295,65,34265,54,0,L|199:47,1,95,4|0,0:0|0:0,0:0:0:0: +76,88,34538,1,8,0:0:0:0: +116,216,34674,1,2,2:0:0:0: +116,216,34742,1,2,2:0:0:0: +116,216,34810,2,0,P|156:244|208:240,1,95,2|0,2:0|0:0,0:0:0:0: +360,152,35083,2,0,P|311:147|268:177,1,95,8|0,0:0|0:0,0:0:0:0: +336,244,35356,54,0,L|384:60,1,190,2|8,2:0|0:0,0:0:0:0: +436,228,35765,1,2,2:0:0:0: +287,76,35901,1,2,2:0:0:0: +462,128,36038,1,2,2:0:0:0: +260,176,36174,1,8,0:0:0:0: +384,60,36310,1,2,2:0:0:0: +360,152,36447,54,0,B|312:224|348:312|348:312|320:300,1,190,4|8,0:0|0:0,0:0:0:0: +204,252,36856,1,2,2:0:0:0: +204,252,36924,1,2,2:0:0:0: +204,252,36992,2,0,P|160:236|104:244,1,95 +232,356,37265,1,8,0:0:0:0: +176,152,37401,1,0,0:0:0:0: +48,332,37538,54,0,L|144:324,1,95,8|2,0:0|2:0,0:0:0:0: +300,244,37810,2,0,L|204:252,1,95,8|2,0:0|2:0,0:0:0:0: +40,320,38083,2,0,L|136:312,1,95,0|2,0:0|2:0,0:0:0:0: +312,252,38356,2,0,P|340:184|308:124,1,142.5,8|0,0:0|0:0,0:0:0:0: +296,120,38629,54,0,P|340:132|390:129,1,95,4|0,0:0|0:0,0:0:0:0: +264,44,38901,2,0,P|224:72|212:116,1,95,8|0,0:0|0:0,0:0:0:0: +252,192,39174,1,2,2:0:0:0: +96,120,39310,1,0,0:0:0:0: +144,284,39447,1,8,0:0:0:0: +256,304,39583,1,2,2:0:0:0: +256,304,39651,1,2,2:0:0:0: +256,304,39719,54,0,B|352:296|420:292|420:292|404:260,1,190,0|8,0:0|0:0,0:0:0:0: +360,196,40129,1,0,0:0:0:0: +320,376,40265,2,0,L|424:371,1,95,2|2,2:0|2:0,0:0:0:0: +456,192,40538,2,0,L|352:197,1,95,8|0,0:0|0:0,0:0:0:0: +416,272,40810,54,0,P|360:192|400:116,1,190,4|8,0:0|0:0,0:0:0:0: +500,84,41219,1,0,0:0:0:0: +380,28,41356,2,0,P|332:20|284:40,1,95,2|0,2:0|0:0,0:0:0:0: +248,164,41629,1,8,0:0:0:0: +120,184,41765,1,2,2:0:0:0: +164,60,41901,54,0,L|68:56,1,95,8|2,0:0|2:0,0:0:0:0: +25,180,42174,2,0,L|121:184,1,95,8|2,0:0|2:0,0:0:0:0: +164,308,42447,1,8,0:0:0:0: +419,236,42583,1,0,0:0:0:0: +398,233,42651,1,0,0:0:0:0: +377,231,42719,1,8,0:0:0:0: +356,230,42788,1,0,0:0:0:0: +335,231,42856,1,8,0:0:0:0: +314,233,42924,1,8,0:0:0:0: +294,237,42992,54,0,P|257:209|249:161,1,95,4|0,0:0|0:0,0:0:0:0: +296,40,43265,1,8,0:0:0:0: +328,168,43401,1,2,2:0:0:0: +328,168,43469,1,2,2:0:0:0: +328,168,43538,2,0,P|376:176|424:152,1,95,2|0,2:0|0:0,0:0:0:0: +480,64,43810,1,8,0:0:0:0: +492,180,43947,1,0,0:0:0:0: +344,320,44083,54,0,P|256:328|197:233,1,190,2|8,2:0|0:0,0:0:0:0: +294,236,44492,1,0,0:0:0:0: +232,160,44629,53,2,2:0:0:0: +244,28,44765,1,2,2:0:0:0: +112,16,44901,1,8,0:0:0:0: +96,148,45038,1,2,2:0:0:0: +96,148,45106,1,0,0:0:0:0: +96,148,45174,54,0,B|72:216|112:268|112:268|88:264,1,142.5,4|0,0:0|0:0,0:0:0:0: +84,264,45447,2,0,P|36:268|0:304,1,95,8|0,0:0|0:0,0:0:0:0: +72,344,45719,2,0,L|272:360,1,190,2|8,2:0|0:0,0:0:0:0: +261,359,46129,1,0,0:0:0:0: +444,296,46265,54,0,P|472:260|472:200,1,95,8|2,0:0|2:0,0:0:0:0: +408,48,46538,2,0,P|397:92|423:146,1,95,8|2,0:0|2:0,0:0:0:0: +384,224,46810,1,2,2:0:0:0: +384,224,46947,2,0,L|288:216,1,95,8|8,0:0|0:0,0:0:0:0: +320,80,47219,1,2,2:0:0:0: +320,80,47288,1,2,2:0:0:0: +320,80,47356,54,0,B|248:68|184:100|184:100|161:85|120:80,1,190,4|8,0:0|0:0,0:0:0:0: +16,144,47765,1,0,0:0:0:0: +4,284,47901,2,0,P|32:323|84:344,1,95,2|0,2:0|0:0,0:0:0:0: +160,368,48174,1,8,0:0:0:0: +268,348,48310,1,0,0:0:0:0: +160,368,48447,54,0,P|206:365|256:364,1,95,2|0,2:0|0:0,0:0:0:0: +440,308,48719,2,0,P|393:305|344:304,1,95,8|0,0:0|0:0,0:0:0:0: +209,229,48992,1,2,2:0:0:0: +232,224,49060,1,0,2:0:0:0: +253,213,49129,1,2,2:0:0:0: +270,196,49197,1,0,2:0:0:0: +282,175,49265,1,8,0:0:0:0: +287,151,49333,1,0,2:0:0:0: +286,127,49401,1,2,2:0:0:0: +279,104,49469,1,2,2:0:0:0: +266,83,49538,54,0,P|312:97|364:86,1,95,4|0,0:0|0:0,0:0:0:0: +195,142,49810,2,0,P|146:137|99:161,1,95,8|0,0:0|0:0,0:0:0:0: +84,288,50083,2,0,P|188:304|264:256,1,190,2|8,2:0|0:0,0:0:0:0: +160,224,50492,1,0,0:0:0:0: +195,142,50629,53,8,0:0:0:0: +172,60,50765,1,2,2:0:0:0: +272,120,50901,53,8,0:0:0:0: +293,37,51038,1,2,2:0:0:0: +348,132,51174,53,8,0:0:0:0: +407,70,51310,1,2,2:0:0:0: +414,174,51447,53,8,0:0:0:0: +416,179,51515,1,8,0:0:0:0: +419,184,51583,1,8,0:0:0:0: +421,189,51651,1,8,0:0:0:0: +424,195,51719,38,0,P|432:231|432:267,1,71.2500027179719,0|0,1:0|0:0,0:0:0:0: +412,356,51992,2,0,P|378:353|336:352,1,71.2500027179719,0|0,1:0|0:0,0:0:0:0: +180,296,52265,1,0,1:0:0:0: +180,296,52401,1,0,1:0:0:0: +180,296,52538,2,0,P|217:294|252:293,1,71.2500027179719,0|0,1:0|0:0,0:0:0:0: +316,216,52810,101,4,3:0:0:0: +280,124,52947,1,0,0:0:0:0: +280,124,53015,1,0,0:0:0:0: +280,124,53083,1,2,0:0:0:0: +212,196,53219,5,4,3:0:0:0: +176,104,53356,1,0,0:0:0:0: +176,104,53424,1,0,0:0:0:0: +176,104,53492,1,2,0:0:0:0: +116,176,53629,1,0,1:0:0:0: +168,308,53765,5,2,0:0:0:0: +168,308,53901,2,0,P|232:300|312:300,1,142.5,4|0,3:0|0:0,0:0:0:0: +368,348,54447,2,0,P|384:312|372:256,1,95,0|0,1:0|1:0,0:0:0:0: +320,204,54856,1,0,0:0:0:0: +448,240,54992,54,0,L|464:184,1,47.5,4|4,3:0|3:0,3:0:0:0: +392,152,55265,2,0,L|388:100,1,47.5,0|4,1:0|3:0,0:0:0:0: +312,80,55538,2,0,L|284:32,1,47.5,0|4,0:0|3:0,0:0:0:0: +228,116,55810,2,0,P|188:112|136:112,1,71.25,0|0,1:0|0:0,0:0:0:0: +156,111,56083,54,0,P|152:152|100:192,1,95,4|0,3:0|0:0,0:0:0:0: +36,184,56492,1,0,0:0:0:0: +72,252,56629,1,0,1:0:0:0: +80,120,56765,1,4,3:0:0:0: +52,48,56901,53,0,1:0:0:0: +114,188,57038,2,0,P|184:184|256:184,1,142.5,0|2,0:0|0:0,0:0:0:0: +352,284,57583,1,0,0:0:0:0: +352,284,57719,1,2,0:0:0:0: +352,284,57856,1,0,0:0:0:0: +352,284,57992,2,0,L|336:344,1,47.5,2|2,0:0|0:0,0:0:0:0: +352,284,58265,54,0,P|288:277|208:276,1,142.5,4|4,3:0|3:0,0:0:0:0: +209,275,58810,2,0,P|180:240|180:184,1,95,0|0,1:0|1:0,0:0:0:0: +204,120,59219,1,0,1:0:0:0: +252,212,59356,54,0,L|304:196,1,47.5,4|0,3:0|0:0,0:0:0:0: +456,76,59629,2,0,L|404:92,1,47.5,0|4,1:0|3:0,0:0:0:0: +260,220,59901,2,0,L|312:204,1,47.5,0|4,0:0|3:0,0:0:0:0: +464,84,60174,2,0,L|412:100,1,47.5,0|0,1:0|0:0,0:0:0:0: +364,152,60447,54,0,P|320:128|268:136,1,95,4|2,3:0|0:0,0:0:0:0: +304,208,60856,1,0,0:0:0:0: +432,289,60992,1,0,1:0:0:0: +415,287,61060,1,0,0:0:0:0: +398,286,61129,1,4,3:0:0:0: +381,285,61197,1,0,0:0:0:0: +364,286,61265,1,0,1:0:0:0: +263,356,61401,1,0,0:0:0:0: +263,356,61469,1,0,0:0:0:0: +263,356,61538,53,0,0:0:0:0: +246,352,61606,1,0,0:0:0:0: +229,349,61674,1,0,0:0:0:0: +212,348,61742,1,0,0:0:0:0: +195,348,61810,1,0,0:0:0:0: +60,362,61947,1,0,0:0:0:0: +48,343,62015,1,0,0:0:0:0: +39,323,62083,1,0,0:0:0:0: +34,302,62151,1,0,0:0:0:0: +34,280,62219,1,0,0:0:0:0: +38,259,62288,1,0,0:0:0:0: +46,239,62356,1,0,0:0:0:0: +46,239,62629,54,0,B|97:232|157:220|157:220|183:242,1,142.5,4|4,3:0|3:0,0:0:0:0: +92,156,63174,2,0,L|189:138,1,95,0|0,1:0|1:0,0:0:0:0: +256,100,63583,1,0,1:0:0:0: +256,176,63719,53,4,3:0:0:0: +256,176,63856,1,4,3:0:0:0: +256,24,63992,1,0,1:0:0:0: +326,138,64129,2,0,L|420:156,1,95,4|4,3:0|3:0,0:0:0:0: +476,208,64538,2,0,P|492:256|476:304,1,95,0|0,1:0|1:0,0:0:0:0: +480,297,65083,1,0,0:0:0:0: +328,224,65219,1,0,0:0:0:0: +340,304,65356,53,0,1:0:0:0: +208,324,65492,1,4,3:0:0:0: +340,304,65629,2,0,P|288:296|260:264,1,95,0|4,1:0|3:0,1:0:0:0: +260,188,66038,1,0,0:0:0:0: +192,224,66174,2,0,L|136:216,1,47.5,0|4,1:0|3:0,0:0:0:0: +24,196,66447,53,2,0:0:0:0: +24,196,66583,1,4,3:0:0:0: +24,196,66719,1,0,1:0:0:0: +24,196,66856,2,0,P|0:152|8:104,1,95,4|0,3:0|0:0,0:0:0:0: +76,140,67265,2,0,P|122:137|172:132,1,95,2|0,0:0|1:0,0:0:0:0: +332,172,67674,1,4,3:0:0:0: +332,172,67810,2,0,L|284:176|236:180,1,95,0|0,1:0|0:0,0:0:0:0: +128,240,68219,1,0,0:0:0:0: +232,276,68356,1,0,1:0:0:0: +120,332,68492,1,4,3:0:0:0: +224,372,68629,54,0,P|268:376|320:356,1,95,4|0,3:0|1:0,0:0:0:0: +360,300,69038,1,4,3:0:0:0: +432,276,69174,2,0,P|452:232|440:180,1,95,2|0,0:0|1:0,0:0:0:0: +344,44,69583,2,0,P|340:92|372:140,1,95,4|0,3:0|0:0,0:0:0:0: +448,88,69992,1,0,1:0:0:0: +448,88,70265,21,0,0:0:0:0: +419,356,70401,1,0,0:0:0:0: +151,324,70538,1,0,0:0:0:0: +180,56,70674,1,0,0:0:0:0: +180,56,71219,37,0,0:0:0:0: +256,300,71356,2,0,L|224:192,1,104.500003189087,4|0,0:0|0:0,0:0:0:0: +202,130,71629,1,8,0:0:0:0: +252,24,71765,53,0,0:0:0:0: +151,324,71901,2,0,L|192:208,1,104.500003189087 +220,124,72174,1,8,0:0:0:0: +352,56,72310,53,0,0:0:0:0: +20,216,72447,2,0,L|124:172,1,104.500003189087 +236,116,72719,1,8,0:0:0:0: +350,191,72856,53,0,0:0:0:0: +468,120,72992,1,0,0:0:0:0: +472,256,73129,1,0,0:0:0:0: +388,120,73265,1,8,0:0:0:0: +511,187,73401,1,0,0:0:0:0: +392,256,73538,54,0,P|284:228|288:144,1,209.000006378174,4|8,0:0|0:0,0:0:0:0: +168,264,73947,1,0,0:0:0:0: +350,191,74083,2,0,P|297:189|236:188,1,104.500003189087 +468,120,74356,2,0,P|415:118|354:117,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +172,168,74629,54,0,P|184:236|264:260,1,156.750004783631 +172,168,74901,2,0,P|119:164|60:164,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +20,236,75174,53,0,0:0:0:0: +80,56,75310,1,0,0:0:0:0: +248,132,75447,2,0,P|298:125|352:124,1,104.500003189087,8|8,0:0|0:0,0:0:0:0: +351,123,75651,1,8,0:0:0:0: +351,123,75719,54,0,B|348:186|340:240|340:240|352:251|364:276,1,156.750004783631,12|0,0:0|0:0,0:0:0:0: +372,288,75993,2,0,L|384:180,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +280,316,76265,2,0,P|232:340|168:328,1,104.500003189087 +220,260,76538,1,8,0:0:0:0: +344,368,76674,53,0,0:0:0:0: +396,176,76810,2,0,L|384:284,1,104.500003189087 +456,348,77083,1,8,0:0:0:0: +308,200,77219,53,0,0:0:0:0: +472,252,77356,1,0,0:0:0:0: +452,116,77492,1,0,0:0:0:0: +352,24,77629,1,8,0:0:0:0: +216,28,77765,1,0,0:0:0:0: +112,116,77901,54,0,B|76:216|144:288|144:288|148:256,1,209.000006378174,4|8,0:0|0:0,0:0:0:0: +196,80,78310,1,0,0:0:0:0: +372,200,78447,2,0,B|332:164|284:148|284:148|224:156|180:184,1,209.000006378174,0|0,0:0|0:0,0:0:0:0: +36,268,78856,1,0,0:0:0:0: +92,368,78992,53,8,0:0:0:0: +110,362,79060,1,0,0:0:0:0: +128,357,79129,1,0,0:0:0:0: +146,353,79197,1,0,0:0:0:0: +165,350,79265,1,0,0:0:0:0: +183,348,79333,1,0,0:0:0:0: +201,346,79401,1,8,0:0:0:0: +219,345,79469,1,0,0:0:0:0: +237,345,79538,1,0,0:0:0:0: +208,236,79674,1,0,0:0:0:0: +370,255,79810,1,8,0:0:0:0: +386,262,79879,1,0,0:0:0:0: +402,268,79947,1,0,0:0:0:0: +417,278,80015,1,0,0:0:0:0: +431,287,80083,6,0,P|408:243|424:184,1,104.500003189087,4|0,0:0|0:0,0:0:0:0: +486,234,80356,1,8,0:0:0:0: +374,350,80492,1,0,0:0:0:0: +286,215,80629,54,0,P|288:159|324:117,1,104.500003189087 +368,192,80901,1,8,0:0:0:0: +182,243,81038,1,0,0:0:0:0: +152,76,81174,54,0,P|168:28|221:-2,1,104.500003189087 +262,103,81447,1,8,0:0:0:0: +105,201,81583,1,0,0:0:0:0: +56,116,81719,53,0,0:0:0:0: +20,248,81856,1,0,0:0:0:0: +152,280,81992,1,8,0:0:0:0: +192,152,82129,1,0,0:0:0:0: +20,248,82265,54,0,P|8:320|64:380,1,156.750004783631 +60,378,82538,2,0,P|114:374|168:372,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +260,336,82810,2,0,P|280:284|268:232,1,104.500003189087 +260,336,83083,1,8,0:0:0:0: +192,152,83219,1,0,0:0:0:0: +105,201,83356,54,0,P|54:203|-7:209,1,104.500003189087 +56,52,83629,2,0,P|107:54|168:60,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +312,216,83901,2,0,P|368:184|376:96,1,156.750004783631 +360,88,84174,2,0,P|416:92|472:76,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +380,284,84447,54,0,P|312:216|200:216,1,209.000006378174,4|8,0:0|0:0,0:0:0:0: +256,340,84856,1,0,0:0:0:0: +164,364,84992,2,0,P|100:356|64:328,1,104.500003189087 +16,208,85265,1,8,0:0:0:0: +140,260,85401,53,0,0:0:0:0: +92,132,85538,2,0,P|120:88|172:64,1,104.500003189087 +192,168,85810,1,8,0:0:0:0: +192,168,85947,1,0,0:0:0:0: +280,64,86083,53,0,0:0:0:0: +404,120,86219,1,0,0:0:0:0: +392,256,86356,1,8,0:0:0:0: +260,284,86492,1,0,0:0:0:0: +304,176,86629,53,0,0:0:0:0: +288,172,87174,53,0,0:0:0:0: +272,168,87719,69,8,0:0:0:0: +257,174,87788,1,0,0:0:0:0: +241,178,87856,2,0,P|202:170|160:168,1,80.750001540184 +136,284,88129,1,8,0:0:0:0: +151,290,88197,1,0,0:0:0:0: +167,294,88265,2,0,P|206:286|248:284,1,80.750001540184 +57,253,88538,1,8,0:0:0:0: +57,253,88810,6,0,P|80:300|72:348,1,95,4|0,0:0|0:0,0:0:0:0: +0,312,89083,1,8,0:0:0:0: +148,344,89219,1,2,2:0:0:0: +96,176,89356,2,0,P|48:168|0:192,1,95,2|0,2:0|0:0,0:0:0:0: +160,232,89629,1,8,0:0:0:0: +148,344,89765,1,0,0:0:0:0: +176,144,89901,54,0,P|264:136|323:231,1,190,2|8,2:0|0:0,0:0:0:0: +336,292,90310,1,0,0:0:0:0: +252,260,90447,53,2,2:0:0:0: +408,236,90583,1,2,2:0:0:0: +244,272,90719,1,8,0:0:0:0: +420,224,90856,1,2,2:0:0:0: +420,224,90924,1,0,0:0:0:0: +420,224,90992,2,0,B|452:152|400:88,1,142.5,4|0,0:0|0:0,0:0:0:0: +401,90,91265,2,0,P|359:95|296:100,1,95,8|0,0:0|0:0,0:0:0:0: +504,64,91538,2,0,L|304:20,1,190,2|8,2:0|0:0,0:0:0:0: +220,80,91947,1,0,0:0:0:0: +60,48,92083,54,0,P|32:84|32:144,1,95,8|2,0:0|2:0,0:0:0:0: +112,292,92356,2,0,P|123:248|97:194,1,95,8|2,0:0|2:0,0:0:0:0: +32,256,92629,1,8,0:0:0:0: +112,116,92765,1,2,2:0:0:0: +208,236,92901,1,8,0:0:0:0: +192,332,93038,1,2,2:0:0:0: +192,332,93106,1,2,2:0:0:0: +192,332,93174,54,0,B|264:344|328:312|328:312|351:327|392:332,1,190,12|8,0:0|0:0,0:0:0:0: +508,268,93583,1,0,0:0:0:0: +384,184,93719,2,0,P|348:152|336:104,1,95,2|0,2:0|0:0,0:0:0:0: +352,16,93992,1,8,0:0:0:0: +244,36,94129,1,0,0:0:0:0: +352,16,94265,54,0,P|306:19|256:20,1,95,2|0,2:0|0:0,0:0:0:0: +228,184,94538,2,0,P|275:187|324:188,1,95,8|0,0:0|0:0,0:0:0:0: +292,104,94810,53,2,2:0:0:0: +144,116,94947,1,2,2:0:0:0: +217,80,95083,53,8,0:0:0:0: +120,192,95219,1,2,2:0:0:0: +156,264,95356,54,0,P|110:250|58:261,1,95,4|0,0:0|0:0,0:0:0:0: +124,352,95629,2,0,P|173:357|220:333,1,95,8|0,0:0|0:0,0:0:0:0: +412,228,95901,2,0,P|308:212|232:260,1,190,2|8,2:0|0:0,0:0:0:0: +340,308,96310,1,0,0:0:0:0: +389,146,96447,37,8,0:0:0:0: +412,228,96583,1,2,2:0:0:0: +300,124,96719,101,8,0:0:0:0: +323,206,96856,1,2,2:0:0:0: +212,104,96992,37,8,0:0:0:0: +235,186,97129,1,2,2:0:0:0: +148,304,97265,53,8,0:0:0:0: +148,304,97333,1,8,0:0:0:0: +148,304,97401,1,8,0:0:0:0: +148,304,97469,1,8,0:0:0:0: +148,304,97538,86,0,P|112:296|72:288,1,71.2500027179719,0|0,1:0|0:0,0:0:0:0: +28,184,97810,2,0,P|65:177|107:173,1,71.2500027179719,0|0,1:0|0:0,1:0:0:0: +232,152,98083,1,0,1:0:0:0: +232,152,98219,1,0,1:0:0:0: +232,152,98356,2,0,P|222:185|217:223,1,71.2500027179719,0|0,1:0|0:0,0:0:0:0: +248,352,98629,101,4,3:0:0:0: +328,324,98765,1,0,0:0:0:0: +328,324,98833,1,0,0:0:0:0: +328,324,98901,1,2,0:0:0:0: +476,288,99038,5,4,3:0:0:0: +392,268,99174,1,0,0:0:0:0: +392,268,99242,1,0,0:0:0:0: +392,268,99310,1,2,0:0:0:0: +448,212,99447,1,0,1:0:0:0: +264,272,99583,5,2,0:0:0:0: +264,272,99719,2,0,P|256:208|312:160,1,142.5,4|0,3:0|0:0,0:0:0:0: +392,156,100265,2,0,P|381:118|335:82,1,95,0|0,1:0|1:0,0:0:0:0: +264,96,100674,1,0,0:0:0:0: +192,72,100810,54,0,L|136:56,1,47.5,4|0,3:0|0:0,0:0:0:0: +32,96,101083,2,0,L|88:112,1,47.5,0|4,1:0|3:0,0:0:0:0: +168,192,101356,2,0,L|112:176,1,47.5,0|4,0:0|3:0,0:0:0:0: +60,228,101629,2,0,P|56:268|56:320,1,71.25,0|0,1:0|0:0,0:0:0:0: +55,299,101901,54,0,P|100:280|144:288,1,95,4|2,3:0|0:0,0:0:0:0: +108,356,102310,1,0,0:0:0:0: +184,356,102447,1,0,1:0:0:0: +188,224,102583,1,4,3:0:0:0: +224,288,102719,53,0,1:0:0:0: +117,193,102856,2,0,P|173:149|245:173,1,142.5,4|8,3:0|1:0,0:0:0:0: +308,304,103401,1,4,3:0:0:0: +308,304,103538,1,0,1:0:0:0: +308,304,103674,1,4,3:0:0:0: +308,304,103810,2,0,L|324:244,1,47.5,0|0,1:0|0:0,0:0:0:0: +308,304,104083,54,0,P|340:352|424:336,1,142.5,4|4,3:0|3:0,0:0:0:0: +456,280,104629,2,0,P|452:231|448:180,1,95,0|0,1:0|1:0,0:0:0:0: +440,108,105038,1,2,0:0:0:0: +340,44,105174,54,0,L|288:60,1,47.5,4|0,3:0|0:0,0:0:0:0: +172,32,105447,2,0,L|188:84,1,47.5,0|4,1:0|3:0,0:0:0:0: +164,192,105719,2,0,L|216:176,1,47.5,0|4,0:0|3:0,0:0:0:0: +324,208,105992,2,0,L|308:156,1,47.5,0|0,1:0|0:0,0:0:0:0: +252,112,106265,54,0,P|208:88|156:96,1,95,4|2,3:0|0:0,0:0:0:0: +192,164,106674,1,0,0:0:0:0: +48,240,106810,1,0,1:0:0:0: +65,242,106879,1,0,0:0:0:0: +82,243,106947,1,4,3:0:0:0: +99,244,107015,1,0,0:0:0:0: +116,243,107083,1,0,1:0:0:0: +180,324,107219,1,0,0:0:0:0: +180,324,107288,1,0,0:0:0:0: +180,324,107356,53,0,0:0:0:0: +197,320,107424,1,0,0:0:0:0: +214,317,107492,1,0,0:0:0:0: +231,316,107560,1,0,0:0:0:0: +248,316,107629,1,0,0:0:0:0: +364,367,107765,1,0,0:0:0:0: +372,347,107833,1,0,0:0:0:0: +376,326,107901,1,0,0:0:0:0: +376,304,107969,1,0,0:0:0:0: +371,283,108038,1,0,0:0:0:0: +362,263,108106,1,0,0:0:0:0: +350,244,108174,1,0,0:0:0:0: +350,244,108447,54,0,B|361:178|372:120|372:120|344:132,1,142.5,4|4,3:0|3:0,0:0:0:0: +268,256,108992,2,0,L|286:157,1,95,0|0,1:0|1:0,0:0:0:0: +256,92,109401,1,4,3:0:0:0: +208,152,109538,53,4,3:0:0:0: +208,152,109674,1,0,0:0:0:0: +88,76,109810,1,0,1:0:0:0: +84,216,109947,2,0,P|36:212|-4:180,1,95,4|4,3:0|3:0,0:0:0:0: +64,144,110356,1,0,1:0:0:0: +24,320,110629,54,0,P|72:321|124:324,1,95,4|2,3:0|0:0,0:0:0:0: +196,348,111038,1,0,0:0:0:0: +184,272,111174,53,0,1:0:0:0: +316,252,111310,1,4,3:0:0:0: +184,272,111447,2,0,P|236:280|264:312,1,95,0|4,1:0|3:0,0:0:0:0: +292,380,111856,1,0,0:0:0:0: +344,324,111992,2,0,L|400:332,1,47.5,0|4,1:0|3:0,0:0:0:0: +488,248,112265,53,2,0:0:0:0: +488,248,112401,1,4,3:0:0:0: +488,248,112538,1,0,1:0:0:0: +488,248,112674,2,0,P|512:204|504:156,1,95,4|0,3:0|0:0,0:0:0:0: +456,104,113083,2,0,P|410:107|360:112,1,95,2|0,0:0|1:0,0:0:0:0: +308,232,113492,1,4,3:0:0:0: +308,232,113629,2,0,P|268:204|212:212,1,95,0|4,1:0|3:0,0:0:0:0: +248,280,114038,1,0,0:0:0:0: +128,348,114174,1,0,1:0:0:0: +148,220,114310,1,4,3:0:0:0: +76,184,114447,54,0,P|108:150|163:141,1,95,4|0,3:0|1:0,0:0:0:0: +228,164,114856,1,0,0:0:0:0: +288,116,114992,2,0,P|336:104|384:112,1,95,4|0,3:0|1:0,0:0:0:0: +468,244,115401,2,0,P|468:196|440:148,1,95,4|0,3:0|0:0,0:0:0:0: +376,188,115810,1,0,1:0:0:0: +376,188,116083,85,0,0:0:0:0: +248,324,116219,1,0,0:0:0:0: +164,156,116356,1,0,0:0:0:0: +288,16,116492,1,0,0:0:0:0: +288,16,117038,53,0,0:0:0:0: +196,56,117174,2,0,P|124:48|48:48,1,142.500005435944,4|0,0:0|0:0,0:0:0:0: +16,140,117583,1,0,0:0:0:0: +72,232,117719,2,0,P|116:256|180:248,1,106.875004076958,8|0,0:0|0:0,0:0:0:0: +184,248,117992,2,0,P|221:249|256:264,1,71.2500027179719,0|0,0:0|0:0,0:0:0:0: +300,340,118265,6,0,P|372:356|440:320,1,142.500005435944 +448,240,118674,1,0,0:0:0:0: +408,160,118810,101,8,0:0:0:0: +464,92,118947,1,0,0:0:0:0: +324,144,119083,5,0,0:0:0:0: +408,160,119219,1,0,0:0:0:0: +256,192,119356,102,0,P|188:196|112:200,1,142.500005435944 +56,120,119765,1,0,0:0:0:0: +72,288,119901,2,0,P|126:282|184:300,1,106.875004076958,8|0,0:0|0:0,0:0:0:0: +192,304,120174,2,0,P|230:306|264:296,1,71.2500027179719 +352,240,120447,6,0,P|416:208|488:228,1,142.500005435944 +428,308,120856,1,0,0:0:0:0: +460,128,120992,101,8,0:0:0:0: +424,36,121129,1,0,0:0:0:0: +360,112,121265,1,0,0:0:0:0: +296,72,121401,1,0,0:0:0:0: +296,72,121469,1,0,0:0:0:0: +296,72,121538,6,0,P|248:96|180:88,1,106.875004076958,4|0,0:0|0:0,0:0:0:0: +194,19,121810,2,0,P|228:5|268:6,1,71.2500027179719 +136,148,122083,102,0,P|106:192|43:219,1,106.875004076958,8|0,0:0|0:0,0:0:0:0: +23,153,122356,2,0,P|45:123|76:103,1,71.2500027179719 +184,256,122629,6,0,P|236:254|284:278,1,106.875004076958 +253,347,122901,2,0,P|218:343|184:329,1,71.2500027179719 +240,164,123174,101,8,0:0:0:0: +204,88,123310,1,0,0:0:0:0: +324,180,123447,5,0,0:0:0:0: +240,164,123583,1,0,0:0:0:0: +292,100,123719,6,0,P|392:132|328:264,1,285,4|0,3:0|0:0,0:0:0:0: +324,180,124674,101,0,0:0:0:0: +324,180,124810,1,0,0:0:0:0: +248,264,124947,1,0,0:0:0:0: +216,152,125083,1,0,0:0:0:0: +136,232,125219,1,0,0:0:0:0: +104,124,125356,1,0,0:0:0:0: +24,200,125492,1,0,0:0:0:0: +72,352,125629,37,0,0:0:0:0: +72,352,125765,1,0,0:0:0:0: +72,352,125901,2,0,P|108:346|152:344,1,71.25,2|0,2:0|0:0,0:0:0:0: +156,324,126174,6,0,P|108:340|48:332,1,104.500003189087 +248,264,126447,2,0,P|300:258|348:274,1,104.500003189087,0|0,0:0|0:0,0:0:0:0: +432,368,126719,1,8,0:0:0:0: +464,248,126856,53,0,0:0:0:0: +292,340,126992,2,0,L|308:184,1,156.750004783631,0|0,0:0|0:0,0:0:0:0: +312,164,127265,2,0,P|300:108|264:76,1,104.500003189087,8|0,0:0|0:0,0:0:0:0: +220,156,127538,1,0,0:0:0:0: +368,76,127674,1,0,0:0:0:0: +200,152,127810,1,8,0:0:0:0: +388,80,127947,53,8,0:0:0:0: +400,96,128015,1,8,0:0:0:0: +412,112,128083,1,8,0:0:0:0: +412,112,128356,54,0,P|348:144|288:96,1,142.5,12|0,0:0|0:0,0:0:0:0: +284,92,128629,2,0,P|238:93|188:88,1,95 +24,176,128901,2,0,P|73:173|120:172,1,95,8|0,0:0|0:0,0:0:0:0: +200,244,129174,53,0,0:0:0:0: +152,384,129310,1,0,0:0:0:0: +304,356,129447,1,8,0:0:0:0: +220,324,129583,1,0,0:0:0:0: +268,188,129719,1,8,0:0:0:0: +288,184,129788,1,0,0:0:0:0: +308,184,129856,1,0,0:0:0:0: +325,188,129924,1,0,0:0:0:0: +342,195,129992,1,2,0:0:0:0: +342,195,130265,54,0,P|320:152|324:100,1,95,4|0,0:0|0:0,0:0:0:0: +428,268,130538,1,8,0:0:0:0: +312,304,130674,1,0,0:0:0:0: +408,92,130810,54,0,P|368:32|284:28,1,142.5,0|0,0:0|0:0,0:0:0:0: +280,32,131083,2,0,P|233:27|180:32,1,95,8|0,0:0|0:0,0:0:0:0: +164,42,131288,1,0,0:0:0:0: +141,54,131356,54,0,B|150:142|150:142|132:163|124:192,1,142.5 +120,204,131629,2,0,P|168:180|208:180,1,95,8|0,0:0|0:0,0:0:0:0: +308,236,131901,53,0,0:0:0:0: +136,320,132038,1,0,0:0:0:0: +228,152,132174,1,8,0:0:0:0: +256,344,132310,1,2,2:0:0:0: +256,344,132379,1,2,2:0:0:0: +256,344,132447,54,0,B|305:335|352:332|352:332|372:280|424:260,1,190,4|8,0:0|0:0,0:0:0:0: +300,256,132856,2,0,P|255:261|204:268,1,95 +120,204,133129,1,0,0:0:0:0: +212,164,133265,53,8,0:0:0:0: +164,20,133401,1,0,0:0:0:0: +312,48,133538,54,0,L|344:88,1,47.5,8|0,0:0|0:0,0:0:0:0: +504,120,133674,2,0,L|453:127,1,47.5 +344,252,133810,2,0,L|362:204,1,47.5 +428,32,133947,2,0,L|409:79,1,47.5,8|0,0:0|0:0,0:0:0:0: +461,234,134083,2,0,L|429:194,1,47.5 +267,161,134219,2,0,L|317:153,1,47.5 +388,140,134356,1,8,0:0:0:0: +394,142,134424,1,0,0:0:0:0: +399,145,134492,1,0,0:0:0:0: +405,148,134560,1,0,0:0:0:0: +411,152,134629,102,0,P|439:208|407:280,1,142.5,4|0,0:0|0:0,0:0:0:0: +372,340,134901,2,0,P|324:341|276:344,1,95,8|0,0:0|0:0,0:0:0:0: +56,308,135174,53,0,0:0:0:0: +200,208,135310,1,8,0:0:0:0: +204,384,135447,1,0,0:0:0:0: +276,244,135583,53,0,0:0:0:0: +128,348,135719,1,0,0:0:0:0: +128,168,135856,2,0,P|168:128|212:124,1,95,0|8,0:0|0:0,0:0:0:0: +20,228,136129,53,0,0:0:0:0: +200,208,136265,1,0,0:0:0:0: +8,128,136401,1,8,0:0:0:0: +186,108,136538,1,0,0:0:0:0: +116,274,136674,1,0,0:0:0:0: +92,64,136810,54,0,B|99:117|112:160|112:160|104:180|96:208,1,142.5,4|0,0:0|0:0,0:0:0:0: +116,274,137083,2,0,P|166:268|212:268,1,95,8|0,0:0|0:0,0:0:0:0: +372,352,137356,1,0,0:0:0:0: +210,267,137492,1,0,0:0:0:0: +372,352,137629,2,0,P|420:340|452:304,1,95,8|0,0:0|0:0,0:0:0:0: +424,216,137901,53,0,0:0:0:0: +448,56,138038,1,0,0:0:0:0: +352,160,138174,5,8,0:0:0:0: +280,16,138310,1,0,0:0:0:0: +264,152,138447,5,0,0:0:0:0: +120,76,138583,1,0,0:0:0:0: +180,188,138719,5,8,0:0:0:0: +24,224,138856,1,0,0:0:0:0: +136,268,138992,70,0,P|184:268|232:272,1,95,4|0,0:0|0:0,0:0:0:0: +332,284,139265,1,8,0:0:0:0: +272,364,139401,53,0,0:0:0:0: +292,188,139538,2,0,P|332:124|408:120,1,142.5 +376,200,139810,2,0,P|425:195|480:188,1,95,8|0,0:0|0:0,0:0:0:0: +368,372,140083,54,0,P|320:368|268:364,1,95 +168,352,140356,1,8,0:0:0:0: +232,276,140492,53,0,0:0:0:0: +220,152,140629,1,0,0:0:0:0: +104,108,140765,1,0,0:0:0:0: +8,188,140901,1,8,0:0:0:0: +4,212,140969,1,0,0:0:0:0: +8,236,141038,1,0,0:0:0:0: +20,256,141106,1,0,0:0:0:0: +38,272,141174,53,0,0:0:0:0: +77,284,141719,53,0,0:0:0:0: +116,292,142265,54,0,B|104:196|8:232|-44:140|4:44|60:20|136:16|172:76|172:76|152:96|152:96|163:120|163:120|144:141|144:141|155:165|155:165|136:188|136:188|156:204|172:236|172:236,1,605.62501155138 +168,224,143356,6,0,P|228:188|304:196,1,142.500005435944,0|2,1:0|0:0,0:0:0:0: +298,193,143697,1,0,0:0:0:0: +298,193,143765,1,0,0:0:0:0: +352,264,143901,1,0,0:0:0:0: +424,212,144038,2,0,P|472:152|464:88,2,142.500005435944,2|2|0,0:0|0:0|0:0,0:0:0:0: +388,132,144719,54,0,L|364:48,1,71.2500027179719,0|0,0:0|0:0,0:0:0:0: +296,112,144992,2,0,P|264:96|224:96,2,71.2500027179719,2|0|0,0:0|0:0|0:0,0:0:0:0: +240,176,145401,53,0,0:0:0:0: +344,308,145538,2,0,P|288:348|216:344,1,142.500005435944,2|0,0:0|0:0,0:0:0:0: +260,260,145947,1,2,0:0:0:0: +172,240,146083,53,0,0:0:0:0: +172,240,146219,1,0,0:0:0:0: +172,240,146356,1,0,0:0:0:0: +172,240,146492,2,0,B|150:244|128:252|128:252|86:238|32:260,1,142.500005435944,2|0,0:0|0:0,0:0:0:0: +124,168,146901,2,0,B|146:164|168:156|168:156|210:170|264:148,1,142.500005435944,2|2,0:0|0:0,0:0:0:0: +276,140,147242,1,0,0:0:0:0: +288,132,147310,1,0,0:0:0:0: +448,180,147447,2,0,B|403:182|360:176|360:176|372:192,1,106.875004076958,2|0,0:0|0:0,0:0:0:0: +380,204,147719,54,0,P|420:264|384:336,1,142.500005435944,4|2,0:0|0:0,0:0:0:0: +397,325,148129,1,0,0:0:0:0: +332,268,148265,1,0,0:0:0:0: +276,332,148401,2,0,P|203:327|128:328,2,142.500005435944,2|2|0,0:0|0:0|0:0,0:0:0:0: +48,264,149083,1,0,0:0:0:0: +48,264,149219,2,0,P|28:196|60:136,2,142.500005435944,2|2|0,0:0|0:0|0:0,0:0:0:0: +112,208,149901,53,2,0:0:0:0: +296,256,150174,1,2,0:0:0:0: +296,256,150242,1,0,0:0:0:0: +296,256,150310,2,0,P|336:252|368:232,1,71.2500027179719,0|0,0:0|0:0,0:0:0:0: +388,72,150583,2,0,P|348:76|316:96,1,71.2500027179719,2|0,0:0|0:0,0:0:0:0: +308,172,150856,2,0,L|460:208,1,142.500005435944,2|0,0:0|0:0,0:0:0:0: +446,204,151265,37,2,0:0:0:0: +446,204,151401,1,0,0:0:0:0: +446,204,151538,5,2,0:0:0:0: +446,204,152083,86,0,P|472:248|464:304,1,95,4|0,0:0|0:0,0:0:0:0: +400,384,152356,1,8,0:0:0:0: +392,264,152492,1,2,2:0:0:0: +392,264,152560,1,2,2:0:0:0: +392,264,152629,2,0,P|348:240|300:240,1,95,2|0,2:0|0:0,0:0:0:0: +328,320,152901,1,8,0:0:0:0: +280,160,153038,1,0,0:0:0:0: +328,320,153174,54,0,L|140:344,1,190,2|8,2:0|0:0,0:0:0:0: +64,296,153583,1,0,0:0:0:0: +176,236,153719,2,2,L|224:244,2,47.5,2|2|2,2:0|2:0|2:0,2:0:0:0: +4,184,153992,1,8,0:0:0:0: +116,124,154129,1,2,2:0:0:0: +116,124,154197,1,2,2:0:0:0: +116,124,154265,54,0,P|136:68|128:32,1,95,4|0,0:0|0:0,0:0:0:0: +16,96,154538,2,0,P|-4:152|4:188,1,95,8|0,0:0|0:0,0:0:0:0: +76,288,154810,2,0,P|124:284|176:284,1,95,2|0,2:0|0:0,0:0:0:0: +292,340,155083,1,8,0:0:0:0: +276,208,155219,1,0,0:0:0:0: +380,312,155356,54,0,P|430:304|488:300,1,95,8|2,0:0|2:0,0:0:0:0: +384,232,155629,2,0,P|434:224|492:220,1,95,8|2,0:0|2:0,0:0:0:0: +312,128,155901,2,0,P|262:120|204:116,1,95,8|2,0:0|2:0,0:0:0:0: +308,48,156174,2,0,P|258:40|200:36,1,95,0|2,0:0|2:0,0:0:0:0: +213,36,156379,1,2,2:0:0:0: +213,36,156447,54,0,P|308:35|408:32,1,190,4|8,0:0|0:0,0:0:0:0: +480,88,156856,1,0,0:0:0:0: +384,232,156992,2,0,P|288:236|236:164,1,190,2|8,2:0|0:0,0:0:0:0: +328,152,157401,1,0,0:0:0:0: +264,324,157538,2,0,P|168:324|64:332,1,190,2|8,2:0|0:0,0:0:0:0: +112,248,157947,1,2,2:0:0:0: +112,248,158015,1,2,2:0:0:0: +112,248,158083,53,2,2:0:0:0: +28,144,158219,1,2,2:0:0:0: +100,32,158356,1,8,0:0:0:0: +228,72,158492,1,2,2:0:0:0: +232,204,158629,54,0,P|264:244|320:256,1,95,4|0,0:0|0:0,0:0:0:0: +484,208,158901,2,0,P|436:189|381:206,1,95,8|0,0:0|0:0,0:0:0:0: +160,328,159174,2,0,P|164:256|216:208,1,142.5,2|2,2:0|2:0,0:0:0:0: +232,204,159447,2,0,P|284:208|320:236,1,95,8|0,0:0|0:0,0:0:0:0: +251,295,159719,54,0,L|215:103,1,190,8|8,0:0|0:0,0:0:0:0: +296,64,160129,1,2,2:0:0:0: +128,152,160265,1,2,2:0:0:0: +105,144,160333,1,2,2:0:0:0: +84,131,160401,1,2,2:0:0:0: +67,114,160469,1,0,2:0:0:0: +54,94,160538,1,8,0:0:0:0: +52,117,160606,1,0,2:0:0:0: +48,140,160674,1,2,2:0:0:0: +37,161,160742,1,2,2:0:0:0: +23,179,160810,6,0,P|5:234|16:292,1,113.999996520996,4|8,0:0|0:0,0:0:0:0: +92,240,161083,2,0,P|103:296|142:341,1,113.999996520996,0|8,0:0|0:0,0:0:0:0: +184,264,161356,2,0,P|222:307|278:326,1,113.999996520996,0|8,0:0|0:0,0:0:0:0: +276,240,161629,2,0,P|331:258|388:247,1,113.999996520996,0|8,0:0|0:0,0:0:0:0: +500,184,161901,54,0,P|518:129|507:71,1,113.999996520996,0|8,0:0|0:0,0:0:0:0: +403,107,162174,2,0,P|391:50|352:5,1,113.999996520996,0|8,0:0|0:0,0:0:0:0: +275,87,162447,2,0,P|236:43|180:24,1,113.999996520996,8|0,0:0|0:0,0:0:0:0: +151,127,162719,2,0,P|96:109|38:120,1,113.999996520996,8|0,0:0|0:0,0:0:0:0: +84,292,162992,53,4,0:0:0:0: +188,228,163129,1,8,0:0:0:0: +200,352,163265,2,0,P|260:344|332:344,1,123.499994346619,0|8,0:0|0:0,0:0:0:0: +512,200,163538,53,0,0:0:0:0: +408,136,163674,1,8,0:0:0:0: +396,260,163810,2,0,P|336:252|264:252,1,123.499994346619,0|8,0:0|0:0,0:0:0:0: +240,80,164083,54,0,P|192:32|128:32,1,123.499994346619,0|8,0:0|0:0,0:0:0:0: +64,176,164356,2,0,P|112:224|176:224,1,123.499994346619,0|8,0:0|0:0,0:0:0:0: +244,72,164629,54,0,P|196:24|132:24,1,123.499994346619,0|8,0:0|0:0,0:0:0:0: +56,184,164901,2,8,P|104:232|168:232,1,123.499994346619,8|8,0:0|0:0,0:0:0:0: +328,128,165174,54,0,P|392:120|448:76,1,132.999995941162,4|8,0:0|0:0,0:0:0:0: +360,212,165447,2,0,B|394:206|416:200|416:200|456:216|500:208,1,132.999995941162,0|8,0:0|0:0,0:0:0:0: +328,300,165719,2,0,P|392:320|452:352,1,132.999995941162,0|8,0:0|0:0,0:0:0:0: +184,300,165992,2,0,P|120:320|60:352,1,132.999995941162,0|8,0:0|0:0,0:0:0:0: +152,212,166265,2,0,B|117:206|96:200|96:200|56:216|12:208,1,132.999995941162,0|8,0:0|0:0,0:0:0:0: +184,128,166538,2,0,P|120:120|64:76,1,132.999995941162,0|8,0:0|0:0,0:0:0:0: +256,80,166810,53,8,0:0:0:0: +256,100,166879,1,0,2:0:0:0: +256,120,166947,1,2,2:0:0:0: +256,140,167015,1,2,2:0:0:0: +256,160,167083,1,2,2:0:0:0: +256,256,167219,1,2,2:0:0:0: +256,256,167288,1,0,2:0:0:0: +256,256,167356,53,4,3:0:0:0: +256,276,167901,53,0,0:0:0:0: +256,296,168447,53,0,0:0:0:0: +256,84,168583,1,8,0:0:0:0: +181,265,168719,1,0,0:0:0:0: +330,115,168856,1,8,0:0:0:0: +150,190,168992,1,0,0:0:0:0: +361,190,169129,1,8,0:0:0:0: +181,115,169265,1,0,0:0:0:0: +330,265,169401,1,8,0:0:0:0: +256,192,169538,38,0,L|344:184,1,80.750001540184,4|0,3:0|0:0,0:0:0:0: +256,192,169810,2,0,L|168:200,1,80.750001540184,4|0,3:0|0:0,0:0:0:0: +256,36,170083,1,4,3:0:0:0: +256,36,170219,1,2,0:0:0:0: +256,36,170356,2,0,L|256:116,1,80.750001540184 +208,260,170629,69,2,0:0:0:0: +191,249,170697,1,0,0:0:0:0: +176,240,170765,1,0,0:0:0:0: +256,192,170901,1,0,0:0:0:0: +304,260,171038,37,2,0:0:0:0: +321,249,171106,1,0,0:0:0:0: +336,240,171174,1,0,0:0:0:0: +256,192,171310,1,0,0:0:0:0: +256,332,171447,101,4,3:0:0:0: diff --git a/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu b/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu new file mode 100644 index 0000000000..38db42fddc --- /dev/null +++ b/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu @@ -0,0 +1,1231 @@ +osu file format v9 + +[General] +AudioFilename: Kozato snow.mp3 +AudioLeadIn: 1000 +PreviewTime: 173263 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.5 +Mode: 0 +LetterboxInBreaks: 0 + +[Editor] +Bookmarks: 14893,27933,40766,54427,67053,80300,94167,107414,120247,133919,147166,160424,173673,187336,200172 +DistanceSpacing: 0.5 +BeatDivisor: 4 +GridSize: 8 + +[Metadata] +Title:Rengetsu Ouka +Artist:Kozato snow +Creator:_Kiva +Version:Yuki YukI +Source: +Tags:Kiva Snowy Dream Yumeko Yuki HakuNoKaemi wmfchris aabc271 + +[Difficulty] +HPDrainRate:8 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.8 +SliderTickRate:0.5 + +[Events] +//Background and Video events +0,0,"BG_example.JPG" +//Break Periods +2,54644,66322 +2,67893,79572 +2,107641,119526 +2,160627,172513 +//Storyboard Layer 0 (Background) +Sprite,Background,Centre,"SB\bg2.png",320,240 + S,0,26288,26495,1,1.04096 + F,0,26288,27530,1,0 + S,0,26495,27530,1.04096,1.26624 +Sprite,Background,Centre,"SB\C32.png",320,240 + M,0,41193,54436,259,283,320,240 + F,0,41193,54436,1 + S,0,41193,54436,1.2048,1.13312 + R,0,41193,54436,0,0.1023999 + F,0,54436,55264,1,0 + S,0,54436,55264,1.13312,1.29696 +Sprite,Background,Centre,"SB\bg1.png",320,240 + F,0,27944,,1 + S,0,27944,41193,1,1.327681 + S,0,41193,,1.327681 +Sprite,Background,Centre,"SB\bg3.png",320,240 + F,0,160427,162083,0,1 + S,0,160427,162083,1 + S,0,162083,166829,1,1.035869 + F,0,162083,173677,1,0.9987923 + S,0,166829,173677,1.035869,1.11264 + F,0,173677,173780,0.9987923,0.8605523 +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,1445,2687,0.8,0 + S,0,1445,2687,1,1.16 +Sprite,Foreground,Centre,"SB\Mini Light.png",320,240 + M,0,24631,,140,147 + S,0,24631,24838,0.19104,0.488 + F,0,24631,25045,1,0 + S,0,24838,25045,0.488,0.5903998 +Sprite,Foreground,Centre,"SB\MINI SNOW.png",320,240 + M,0,25045,,472,224 + S,0,25045,25252,0.32416,0.5392001 + F,0,25045,25459,1,0 + S,0,25252,25459,0.5392001,0.6518402 +Sprite,Foreground,Centre,"SB\black.png",320,240 + F,0,26702,27944,0,1 + F,0,27944,28358,1,0.55296 + F,0,28358,28772,0.55296,0.005119934 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25459,,193,293 + F,0,25459,25666,1,0 + S,0,25459,25666,0.61088,1.24576 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25666,,387,373 + P,0,25666,,H + V,0,25666,,1.06144,1 + V,0,25666,,1.06144,1 + F,0,25666,25873,1,0 + S,0,25666,25873,0.6313599,1.4096 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25873,,417,134 + M,0,25873,,417,134 + P,0,25873,,V + F,0,25873,26080,1,0 + S,0,25873,26080,1,1.52224 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + F,0,26080,26288,1,0 + S,0,26080,26288,1,1.54272 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + S,0,26288,26391,0.3,0.7 + F,0,26288,27116,1 + S,0,26391,27530,0.7,0.9 + F,0,27116,27530,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,26288,,529,377 + P,0,26288,,H + S,0,26288,26391,0.4,0.55 + F,0,26288,26702,1,0 + S,0,26391,26702,0.55,0.9 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,26288,,202,210 + P,0,26288,,H + P,0,26288,,V + S,0,26288,26391,0.4,0.64 + F,0,26288,26702,1,0 + S,0,26391,26702,0.64,0.7 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,40779,41193,0,1 + F,0,41193,41814,1,0 +Sprite,Foreground,Centre,"SB\sakura_b",320,240 + F,0,54436,55264,0,1 + R,0,54436,61060,0.2252804,0 + S,0,54443,,1.5625 + M,0,54443,61060,378,177,304,342 + F,0,55264,59404,1 + F,0,59404,61060,1,0 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,61060,61267,0,0.7122133 + S,0,61060,62302,1,1.1024 + F,0,61267,62302,0.7122133,0 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,67685,67892,0,0.8333333 + S,0,67685,68927,1,1.13312 + F,0,67892,68927,0.8333333,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,80935,,305,116 + F,0,80935,81038,1,0.9206253 + S,0,80935,81038,0.3548797,0.7181275 + S,0,81038,,0.7181275 + F,0,81038,81349,0.9206253,0.6809599 + S,0,81038,81556,0.7181275,0.8000475 + F,0,81349,81556,0.6809599,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + F,0,14694,14901,0,0.8333333 + S,0,14694,14901,0.7337599,1.41984 + S,0,14901,15108,1.41984,1.6144 + F,0,14901,15936,0.8333333,0 + S,0,15108,15522,1.6144,1.80896 + S,0,15522,15936,1.80896,1.94208 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,102,25,93,434 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,411,130,387,308 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,102,25,93,434 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,179,179,203,467 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,237,361,274,534 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,0.63136 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,227,175,267,525 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,0.41632 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,564,-67,619,531 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Sprite,Foreground,Centre,"SB\SNOW.png",320,240 + S,0,67685,,2.35168 + F,0,67685,70998,0,1 + M,0,67685,80314,320,-211,317,693 + F,0,68513,80314,1,0 + F,0,70998,79279,1 + F,0,79279,80314,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + F,0,70998,71826,0,1 + M,0,70998,74299,257,66,263,208 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,70998,,0.7030401 + F,0,70998,71826,0,1 + M,0,70998,74310,431,109,422,223 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + F,0,70998,71826,0,1 + M,0,70998,74299,73,-34,66,270 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,70998,,0.6825599 + F,0,70998,71826,0,1 + M,0,70998,74299,390,75,370,115 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,81763,,249,251 + S,0,81763,81866,0.39584,0.6620799 + F,0,81763,82384,1,0 + S,0,81866,82384,0.6620799,0.8975999 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,82591,,152,394 + S,0,82591,82694,0.3651201,0.7952 + F,0,82591,83212,1,0 + S,0,82694,83212,0.7952,1.01024 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,83419,,456,324 + S,0,83419,83523,0.3856,0.744 + F,0,83419,84040,1,0 + S,0,83523,84040,0.744,1.02048 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,85075,,321,197 + S,0,85075,85179,0.37536,0.80544 + F,0,85075,85696,0.9948799,0 + S,0,85179,85696,0.80544,1.12288 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,85903,,248,284 + S,0,85903,86007,0.3344,0.8464 + F,0,85903,86524,1,0 + S,0,86007,86524,0.8464,1.2048 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,86731,,207,101 + S,0,86731,86835,0.37536,0.8 + F,0,86731,87352,1,0 + S,0,86835,87352,0.8463999,1.22528 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,87560,,472,229 + S,0,87560,87663,0.3,0.8 + F,0,87560,88180,1,0 + S,0,87663,88180,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,88388,,408,373 + S,0,88388,88491,0.3,0.8 + F,0,88388,88905,1,0 + S,0,88491,88905,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,89216,,321,136 + S,0,89216,89319,0.3,0.8 + F,0,89216,89733,1,0 + S,0,89319,89733,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,90044,,257,252 + S,0,90044,90147,0.3,0.8 + F,0,90044,90561,1,0 + S,0,90147,90561,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,90872,,265,292 + S,0,90872,90975,0.3,0.8 + F,0,90872,91389,1,0 + S,0,90975,91389,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,91700,,265,132 + S,0,91700,91804,0.3,0.8 + F,0,91700,92217,1,0 + S,0,91804,92217,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,92528,,521,252 + S,0,92528,92632,0.3,0.8 + F,0,92528,93045,1,0 + S,0,92632,93045,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,93356,,304,309 + S,0,93356,93460,0.3,0.8 + F,0,93356,93873,1,0 + S,0,93460,93873,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,84247,,495,122 + S,0,84247,84351,0.3855998,0.7849598 + F,0,84247,84868,1,0 + S,0,84351,84868,0.7849598,1.13312 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + M,0,94184,,320,240 + S,0,94184,94391,1,1.05632 + F,0,94184,95012,1,0 + S,0,94391,95012,1.05632,1.19456 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,100809,102051,0.38048,0 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + R,0,120276,,0 + F,0,120276,120690,0,1 + F,0,120690,121518,1,0 +Sprite,Foreground,Centre,"SB\bg4.png",320,240 + F,0,107441,133112,1 + S,0,107441,133112,1.3,1 + R,0,107441,133112,0.2,0 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,107020,107434,0,1 + F,0,107434,108269,1,0 +Sprite,Foreground,Centre,"SB\C32.png",320,240 + F,0,173682,190322,1 + M,0,173682,200181,228,268,320,240 + S,0,173682,200181,1.70656,1 + R,0,173682,200181,0.3072,0 + F,0,190322,192412,1 + F,0,192412,200181,1 + F,0,200181,201009,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + F,0,173677,173682,0.69792,0.3702399 + S,0,173682,180306,1,1.43008 + R,0,173682,180306,0.04096008,2.7648 + F,0,173682,186103,0.3702399 + S,0,180306,186931,1.43008,1.64512 + R,0,180306,186931,2.7648,4.68992 + F,0,186103,186931,0.3702399,0 +Sprite,Foreground,Centre,"SB\MINI SNOW.png",320,240 + F,0,186931,187759,0,1 + S,0,186931,200181,1,1.53248 + R,0,186931,200181,1.92512,0 + F,0,187759,199353,1 + F,0,199353,200181,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + F,0,186931,188173,0,0.3503307 + S,0,186931,199353,1,1.28672 + R,0,186931,199353,-3.23584,-1.76128 + F,0,188173,198110,0.3503307,0.19104 + F,0,198110,199353,0.19104,0.005120086 + F,0,199353,200724,0.005120086,0 +Animation,Foreground,Centre,"SB\am1\am1.jpg",320,240,5,500,LoopForever + M,0,74310,,313,234 + F,0,74310,74724,0.05792008,1 + F,0,74724,75345,1,0.8813094 + F,0,75345,75966,0.8813094,0.9981386 + F,0,75966,76794,0.9981386,0.8279376 + F,0,76794,77622,0.8279376,0.9956568 + F,0,77622,78450,0.9956568,0.7588959 + F,0,78450,79279,0.7588959,0.9931734 + F,0,79279,80107,0.9931734,0.01013343 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,74310,,313,232 + S,0,74310,74413,0.3139198,0.6415999 + F,0,74310,75138,1,0 + S,0,74413,75138,0.6415999,0.7961942 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,133112,133940,0,1 + F,0,133940,147178,1 + S,0,133940,147178,1,1.16384 + F,0,147178,147592,1,0 + S,0,147178,147592,1.16384,1.33792 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,120690,,320,240 + S,0,120690,120794,1,1.29696 + F,0,120690,121104,1 + S,0,120794,121932,1.29696,1.44032 + F,0,121104,121932,1,0.002415478 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + S,0,160427,160531,0.7337601,1.4608 + F,0,160427,162083,1,0 + S,0,160531,161255,1.4608,1.77824 + S,0,161255,162083,1.77824,2.03615 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + R,0,132284,,0 + F,0,132284,133112,0,1 + F,0,133112,133940,1,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + S,0,133940,134043,1,1.38912 + F,0,133940,134457,1,0.80544 + S,0,134043,134975,1.38912,1.89088 + F,0,134457,134975,0.80544,0 +Sprite,Foreground,Centre,"SB\SNOW.png",320,240 + S,0,108269,,2.37216 + F,0,108269,109097,0,1 + M,0,108269,120173,314,-250,315,229 + F,0,109097,112409,1 + F,0,112409,112823,1,0.80032 + F,0,112823,113237,0.80032,1 + F,0,113237,119344,1 + F,0,119344,120173,1,0 +Sprite,Foreground,Centre,"SB\black.png",320,240 + S,0,173263,,1 + F,0,173263,173682,0,1 + F,0,173682,174510,1,0 + F,0,173682,174510,1,0.004830897 +Sprite,Foreground,Centre,"SB\White Effect.png",320,240 + M,0,173677,,217,128 + F,0,173677,174510,1,0 + S,0,173677,174510,0.2115199,1.09216 +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +1445,414.050100062108,4,2,1,70,1,0 +13659,-100,4,2,1,40,0,0 +13762,-100,4,2,1,45,0,0 +13866,-100,4,2,1,50,0,0 +13970,-100,4,2,1,55,0,0 +14073,-100,4,2,1,50,0,0 +14177,-100,4,2,1,55,0,0 +14280,-100,4,2,1,60,0,0 +14384,-100,4,2,1,75,0,0 +14487,-100,4,2,1,80,0,0 +14591,-100,4,2,1,85,0,0 +14694,-100,4,2,1,60,0,0 +14901,-100,4,2,0,45,0,0 +15005,-100,4,2,1,60,0,0 +15315,-100,4,2,0,45,0,0 +15419,-100,4,2,1,60,0,0 +15626,-100,4,2,0,45,0,0 +15833,-100,4,2,1,60,0,0 +16040,-100,4,2,0,45,0,0 +16247,-100,4,2,1,60,0,0 +16454,-100,4,2,0,45,0,0 +16764,-100,4,2,1,60,0,0 +16868,-100,4,2,0,45,0,0 +17178,-100,4,2,1,60,0,0 +17282,-100,4,2,0,45,0,0 +17489,-100,4,2,1,60,0,0 +17696,-100,4,2,0,45,0,0 +17903,-100,4,2,1,60,0,0 +18110,-100,4,2,0,45,0,0 +18317,-100,4,2,1,60,0,0 +18628,-100,4,2,0,45,0,0 +18731,-100,4,2,1,60,0,0 +18938,-100,4,2,0,45,0,0 +19145,-100,4,2,1,60,0,0 +19352,-100,4,2,0,45,0,0 +19559,-100,4,2,1,60,0,0 +19766,-100,4,2,0,45,0,0 +19973,-100,4,2,1,60,0,0 +20180,-100,4,2,0,45,0,0 +20387,-100,4,2,1,60,0,0 +20594,-100,4,2,0,45,0,0 +20801,-100,4,2,1,60,0,0 +21112,-100,4,2,0,45,0,0 +21215,-100,4,2,1,60,0,0 +21422,-100,4,2,0,45,0,0 +21629,-100,4,2,1,60,0,0 +21836,-100,4,2,0,45,0,0 +22043,-100,4,2,1,60,0,0 +22354,-100,4,2,0,45,0,0 +22458,-100,4,2,1,60,0,0 +22768,-100,4,2,0,45,0,0 +22872,-100,4,2,1,60,0,0 +23079,-100,4,2,0,45,0,0 +23286,-100,4,2,1,60,0,0 +23596,-100,4,2,0,45,0,0 +23700,-100,4,2,1,60,0,0 +23907,-100,4,2,0,45,0,0 +24114,-100,4,2,1,60,0,0 +24321,-100,4,2,0,45,0,0 +24528,-100,4,1,1,60,0,0 +27323,-100,4,2,1,60,0,0 +27530,-100,4,2,1,60,0,0 +27944,-100,4,2,1,60,0,0 +29082,-100,4,2,1,65,0,0 +39951,-100,4,2,1,80,0,0 +40158,-100,4,2,1,85,0,0 +40766,-100,4,2,1,30,0,0 +41193,-100,4,2,1,85,0,0 +41814,-100,4,2,1,85,0,0 +43471,-100,4,2,1,75,0,0 +44092,-100,4,2,1,65,0,0 +44299,-100,4,2,1,55,0,0 +44506,-100,4,2,1,45,0,0 +44920,-100,4,2,1,50,0,0 +45437,-100,4,2,1,55,0,0 +46162,-100,4,2,1,60,0,0 +47094,-100,4,2,1,65,0,0 +47818,-100,4,2,1,55,0,0 +48750,-100,4,2,1,60,0,0 +50095,-100,4,2,1,65,0,0 +54444,414.050100062108,4,2,1,70,1,0 +67383,-100,4,2,1,50,0,0 +67590,-100,4,2,1,70,0,0 +102680,-200,4,2,1,70,0,0 +105993,-100,4,2,1,70,0,0 +107441,414.050100062108,4,2,1,70,1,0 +107648,-100,4,2,1,70,0,0 +120380,-100,4,2,1,75,0,0 +132905,-100,4,2,1,35,0,0 +133008,-100,4,2,1,40,0,0 +133112,-100,4,2,1,45,0,0 +133215,-100,4,2,1,45,0,0 +133319,-100,4,2,1,50,0,0 +133422,-100,4,2,1,55,0,0 +133526,-100,4,2,1,60,0,0 +133629,-100,4,2,1,65,0,0 +133733,-100,4,2,1,70,0,0 +133836,-100,4,2,1,75,0,0 +133940,-100,4,2,1,80,0,1 +136217,-100,4,2,1,60,0,1 +137045,-100,4,2,1,80,0,1 +147174,-100,4,2,1,80,0,0 +147178,414.050100062108,4,2,1,70,1,0 +173263,-100,4,2,1,60,0,0 +173682,414.050100062108,4,2,1,70,1,0 +174303,-100,4,2,1,40,0,0 +174406,-100,4,2,1,45,0,0 +174510,-100,4,2,1,50,0,0 +174613,-100,4,2,1,55,0,0 +174717,-100,4,2,1,60,0,0 +174820,-100,4,2,1,65,0,0 +174924,-100,4,2,1,70,0,0 +175027,-100,4,2,1,75,0,0 +175131,-100,4,2,1,80,0,0 +186517,-100,4,2,1,30,0,0 +186931,-100,4,2,1,80,0,1 +187035,-100,4,2,1,80,0,1 +196247,-100,4,2,1,70,0,1 +196454,-100,4,2,1,60,0,1 +196661,-100,4,2,1,50,0,1 +196868,-100,4,2,1,80,0,1 +200181,-100,4,2,1,80,0,0 + +[Colours] +Combo1 : 152,48,207 +Combo2 : 254,75,169 +Combo3 : 226,5,221 +SliderBorder : 224,224,224 + +[HitObjects] +112,96,1030,1,0 +144,108,1134,1,0 +176,92,1237,1,0 +208,108,1341,1,0 +240,96,1445,1,0 +408,272,2894,5,0 +408,272,2997,1,0 +408,272,3101,2,0,B|360:280|312:272,1,90 +104,276,3515,2,0,B|152:268|200:276,1,90 +216,184,3929,1,0 +168,88,4136,1,0 +256,144,4343,1,0 +208,48,4550,1,0 +312,64,4757,5,0 +416,64,4964,1,0 +416,64,5067,2,0,B|448:104|464:144|464:200,1,135 +463,187,5481,1,0 +463,187,5585,1,0 +437,289,5792,1,0 +337,262,5999,1,0 +364,162,6206,1,0 +464,188,6413,1,0 +464,88,6620,6,0,B|424:64|368:56,1,90 +280,56,7034,2,0,B|184:56,1,90 +190,56,7448,1,0 +128,136,7655,1,0 +120,168,7759,1,0 +128,200,7862,1,0 +136,232,7966,1,0 +128,264,8069,1,0 +192,336,8276,5,0 +192,336,8380,2,0,B|240:336|288:328|336:312,1,135 +324,315,8794,1,0 +324,315,8897,1,0 +336,216,9104,1,0 +440,200,9311,1,0 +480,104,9518,1,0 +392,40,9726,6,0,B|288:40,1,90 +224,88,10140,1,0 +216,104,10243,1,0 +208,120,10347,1,0 +200,136,10450,1,0 +192,152,10554,1,0 +256,296,10761,1,0 +256,152,10968,1,0 +256,296,11175,1,0 +168,344,11382,5,0 +168,248,11589,1,0 +168,248,11692,1,0 +128,112,12003,1,0 +128,112,12106,1,0 +128,112,12210,2,0,B|144:160|128:208,1,90 +48,248,12624,2,0,B|32:200|48:152,1,90 +48,64,13038,5,0 +48,64,13245,2,0,B|96:64,2,45 +208,64,13659,1,0 +240,64,13762,1,0 +272,72,13866,1,0 +304,88,13970,1,0 +328,112,14073,1,8 +344,144,14177,1,8 +352,176,14280,1,8 +352,208,14384,5,0 +344,240,14487,1,0 +328,272,14591,1,0 +304,296,14694,2,0,B|264:320|216:328,1,90,0|2 +104,280,15108,2,0,B|144:256|192:248,1,90,0|2 +280,224,15522,1,0 +296,120,15729,1,2 +192,96,15936,1,0 +168,192,16143,5,2 +168,192,16247,1,0 +168,192,16350,1,0 +144,288,16557,2,0,B|208:288|208:288|232:264,1,90,2|0 +168,192,16971,2,0,B|104:192|104:192|80:216,1,90,2|0 +144,288,17385,1,2 +256,352,17592,1,0 +368,288,17799,1,2 +256,104,18007,5,0 +256,104,18214,1,2 +256,104,18317,2,0,B|256:240,1,135,0|2 +256,239,18731,1,0 +256,239,18835,1,0 +168,296,19042,1,2 +256,352,19249,1,0 +168,296,19456,1,2 +256,239,19663,1,0 +344,296,19870,1,2 +256,239,20077,1,0 +136,152,20284,5,2 +256,96,20491,1,0 +376,152,20698,1,2 +424,264,20905,1,0 +416,264,21008,1,0 +408,264,21112,1,2 +400,264,21215,1,0 +392,264,21319,1,0 +288,264,21526,1,2 +288,264,21629,1,0 +136,208,21940,5,2 +136,208,22043,1,0 +136,208,22147,2,0,B|136:144|136:144|160:128,1,90,0|2 +248,104,22561,2,0,B|248:168|248:168|224:184,1,90,0|2 +136,104,22975,1,0 +248,56,23182,1,2 +360,104,23389,1,0 +376,120,23493,1,0 +392,136,23596,1,2 +408,152,23700,1,0 +424,168,23803,1,0 +432,280,24010,5,2 +256,328,24217,1,0 +80,280,24424,1,2 +256,192,24631,12,0,27116 +256,192,27323,5,0 +256,192,27426,1,0 +256,192,27530,1,4 +72,136,27944,5,0 +161,84,28151,1,0 +158,182,28358,1,0 +96,80,28565,1,0 +192,32,28772,5,0 +224,32,28875,1,0 +256,32,28979,1,0 +288,32,29082,1,0 +320,32,29186,2,0,B|368:32,4,45 +272,112,29807,6,0,B|248:160|272:200,1,90 +360,160,30221,1,0 +368,168,30324,1,0 +376,176,30428,1,0 +384,184,30532,1,0 +392,192,30635,2,0,B|448:192,4,45 +328,280,31256,5,0 +224,296,31463,1,0 +128,248,31670,2,0,B|96:216|104:160,1,90 +176,88,32084,2,0,B|208:120|200:176,1,90 +128,248,32498,2,0,B|96:280,2,45 +216,192,32912,2,0,B|256:168|304:192,1,90 +264,288,33326,2,0,B|304:312|352:288,1,90 +408,208,33740,1,0 +424,104,33947,1,0 +336,40,34154,6,0,B|288:40,4,45,0|0|0|0|8 +336,136,34776,1,0 +176,136,34983,1,0 +256,72,35190,1,0 +256,112,35293,1,0 +256,160,35397,1,0 +88,264,35604,2,0,B|128:240|176:240,1,90 +424,264,36018,2,0,B|384:240|336:240,1,90 +208,320,36432,6,0,B|208:232,1,90 +304,320,36846,2,0,B|304:232,1,90 +256,144,37260,1,0 +256,144,37467,1,0 +256,144,37570,1,0 +256,144,37674,1,0 +336,80,37881,1,0 +424,136,38088,1,0 +424,136,38191,1,0 +424,136,38295,2,0,B|424:232,1,90 +344,272,38709,5,0 +344,176,38916,1,0 +256,328,39123,1,0 +256,120,39330,1,0 +176,224,39537,5,0 +176,224,39744,1,0 +176,224,39848,1,0 +176,224,39951,1,0 +120,128,40158,1,0 +120,128,40262,1,0 +120,128,40365,1,0 +224,128,40572,1,0 +224,128,40676,1,0 +224,128,40779,6,0,B|224:96,7,22.5 +224,105,41193,6,0,B|312:104,1,90,4|0 +400,152,41607,1,0 +400,256,41814,2,0,B|432:288,4,45 +304,312,42435,1,0 +208,256,42642,1,0 +208,256,42746,1,0 +208,256,42850,2,0,B|208:168,1,90 +168,80,43264,6,0,B|208:56|256:80,1,90 +272,112,43574,1,0 +288,144,43678,2,0,B|312:184|368:192,1,90 +368,96,44092,1,0 +368,288,44299,1,0 +280,336,44506,6,0,B|240:368,4,45 +232,248,45127,1,0 +232,248,45230,1,0 +128,248,45437,1,0 +120,248,45541,1,0 +112,248,45644,1,0 +104,248,45748,1,0 +48,168,45955,1,0 +104,80,46162,5,0 +120,80,46265,1,0 +136,80,46369,1,0 +152,80,46472,1,0 +168,80,46576,1,0 +272,80,46783,1,0 +272,80,46886,1,0 +376,80,47094,1,0 +400,80,47197,1,0 +424,80,47301,1,0 +448,80,47404,1,0 +456,176,47611,5,0 +400,256,47818,1,8 +368,256,47922,1,0 +336,256,48025,1,0 +304,256,48129,1,0 +272,256,48232,1,0 +176,256,48439,1,0 +176,256,48543,1,0 +80,256,48750,5,0 +80,256,48853,1,0 +80,256,48957,1,0 +80,256,49060,1,0 +128,168,49267,1,0 +32,168,49474,1,0 +32,168,49578,1,0 +32,168,49681,1,0 +32,168,49785,1,0 +32,168,49888,1,0 +80,80,50095,5,0 +96,72,50199,1,0 +200,40,50406,1,0 +224,40,50509,1,0 +248,40,50613,1,0 +272,40,50716,1,0 +360,96,50923,1,0 +415,175,51131,5,0 +336,230,51338,1,0 +281,151,51545,1,0 +360,96,51752,1,0 +360,96,51855,1,0 +360,96,51959,1,0 +442,214,52166,1,0 +323,297,52373,1,0 +241,178,52580,1,0 +241,178,52683,1,0 +241,178,52787,1,0 +256,192,52890,12,4,54444 +168,112,67072,5,0 +168,112,67176,1,0 +168,112,67279,1,0 +168,112,67486,1,8 +168,112,67693,1,4 +144,96,80322,5,0 +144,96,80425,1,0 +144,96,80529,1,0 +144,96,80736,1,0 +240,56,80943,1,12 +280,152,81150,1,0 +280,152,81253,1,0 +280,152,81357,1,0 +184,192,81564,1,0 +184,192,81771,1,4 +88,232,81978,5,0 +88,232,82185,1,0 +88,232,82392,1,0 +88,336,82599,2,8,B|184:336,1,90,8|0 +208,336,82909,1,0 +240,336,83013,2,0,B|328:336,1,90 +392,264,83427,1,4 +464,168,83634,1,0 +392,264,83841,1,0 +464,168,84048,1,0 +432,64,84255,6,8,B|336:64,1,90,8|0 +80,64,84669,2,8,B|176:64,1,90,0|0 +256,136,85083,1,4 +256,304,85290,1,0 +256,136,85497,1,0 +256,304,85704,1,0 +184,224,85911,5,8 +88,224,86118,1,0 +80,216,86222,1,0 +72,208,86325,1,0 +72,112,86532,1,0 +144,40,86739,2,0,B|240:40,1,90,4|0 +224,128,87153,2,0,B|312:128,1,90 +408,168,87568,6,0,B|432:208|416:256,1,90,8|0 +416,254,87878,1,0 +416,254,87982,1,0 +344,312,88189,1,0 +344,312,88396,1,4 +168,256,88603,1,0 +344,200,88810,1,0 +168,144,89017,1,0 +256,76,89224,5,8 +256,160,89431,1,0 +256,192,89534,1,0 +256,224,89638,1,0 +256,307,89845,1,0 +192,192,90052,1,4 +256,76,90259,1,0 +320,192,90466,1,0 +256,307,90673,1,0 +200,232,90880,5,8 +312,232,91087,1,0 +152,152,91294,1,0 +360,152,91501,1,0 +200,72,91708,5,4 +312,72,91915,1,0 +152,152,92122,1,0 +360,152,92329,1,0 +456,192,92536,5,8 +416,288,92743,1,0 +384,288,92847,1,0 +352,288,92950,1,0 +264,336,93157,2,0,B|240:296|240:240,1,90,0|4 +240,160,93571,1,0 +208,64,93778,1,0 +176,72,93882,1,0 +152,96,93985,1,0 +136,128,94089,1,0 +136,160,94192,5,8 +72,240,94399,2,0,B|16:256,2,45 +136,312,94813,1,0 +192,232,95020,2,0,B|240:152,1,90 +376,112,95434,2,0,B|328:192,1,90 +464,112,96056,5,0 +496,192,96263,1,0 +464,272,96470,1,0 +48,272,96884,1,0 +16,192,97091,1,0 +48,112,97298,1,0 +152,64,97505,5,0 +224,136,97712,1,0 +152,64,97919,1,0 +248,40,98126,1,0 +344,72,98333,2,0,B|392:88|416:144,1,90 +392,224,98747,2,0,B|344:208|320:152,1,90 +224,136,99161,5,0 +152,208,99368,1,0 +144,240,99471,1,0 +152,272,99575,1,0 +248,222,99782,1,0 +248,312,99989,2,0,B|248:216,1,90 +328,112,100403,2,0,B|328:216,1,90 +360,288,100817,5,2 +432,216,101024,1,0 +432,184,101128,1,0 +432,152,101231,1,0 +400,64,101438,1,0 +304,40,101645,2,0,B|256:40|216:72,1,90 +200,168,102059,2,0,B|248:168|288:136,1,90 +176,280,102680,6,0,B|176:336,2,45 +336,104,103508,2,0,B|336:48,2,45 +336,104,104130,5,0 +416,192,104337,1,0 +336,280,104544,1,0 +256,192,104751,1,0 +176,280,104958,1,0 +96,192,105165,1,0 +176,104,105372,1,0 +256,192,105579,1,0 +256,192,105786,5,0 +256,192,105993,2,0,B|256:136,2,45 +256,192,106407,1,0 +256,288,106614,1,0 +256,288,106821,2,0,B|160:288,1,90 +144,288,107131,1,0 +128,288,107235,1,0 +112,288,107338,1,0 +96,288,107441,1,4 +256,56,120276,5,0 +248,88,120380,1,0 +256,120,120483,1,0 +264,152,120587,1,0 +256,184,120690,5,4 +160,208,120897,2,0,B|176:256|224:280,1,90 +296,208,121311,1,0 +312,192,121415,1,0 +336,184,121518,5,0 +360,184,121622,1,0 +376,192,121725,1,0 +392,208,121829,1,0 +400,232,121932,1,0 +376,336,122139,1,0 +376,336,122243,1,0 +376,336,122346,6,0,B|336:360|288:360,1,90 +192,336,122760,2,0,B|168:296|168:248,1,90 +272,208,123174,1,0 +216,104,123381,1,0 +336,88,123588,2,0,B|304:120|304:176,2,90 +416,168,124210,1,0 +416,168,124313,2,0,B|472:192|472:256|424:280,1,135 +432,275,124727,1,0 +432,275,124831,1,0 +344,224,125038,1,0 +240,208,125245,2,0,B|248:160|304:136,1,90 +360,80,125659,6,0,B|328:40|272:56,1,90 +216,144,126073,1,0 +104,136,126280,1,0 +104,136,126383,1,0 +104,136,126487,2,0,B|64:168|64:224,1,90 +136,296,126901,5,0 +160,320,127004,1,0 +192,328,127108,1,0 +224,320,127211,1,0 +248,296,127315,2,0,B|280:264|296:216,1,90 +294,219,127625,1,0 +408,80,127936,5,0 +368,80,128039,1,0 +328,80,128143,1,0 +224,80,128350,1,0 +72,40,128557,1,0 +104,192,128764,1,0 +272,144,128971,5,0 +120,104,129178,1,0 +152,256,129385,1,0 +152,256,129489,1,0 +152,256,129592,1,0 +152,256,129696,1,0 +152,256,129799,2,0,B|200:280|240:256,1,90 +328,128,130213,2,0,B|280:104|240:128,1,90 +240,224,130627,5,0 +336,256,130834,1,0 +320,288,130938,1,0 +296,312,131041,2,0,B|256:344|208:352,1,90 +184,352,131352,1,0 +152,352,131455,1,0 +64,264,131662,1,0 +160,264,131869,1,0 +32,248,132076,1,0 +192,248,132284,1,0 +96,160,132491,1,0 +120,136,132594,1,0 +152,128,132698,1,0 +248,128,132905,5,0 +280,120,133008,1,0 +312,120,133112,1,0 +344,128,133215,1,0 +372,144,133319,1,0 +392,176,133422,1,0 +400,208,133526,1,0 +400,240,133629,1,0 +392,272,133733,1,0 +376,304,133836,1,0 +344,320,133940,6,0,B|304:344|256:344,1,90,4|0 +192,280,134354,1,0 +92,264,134561,1,0 +80,232,134664,1,0 +76,196,134768,1,0 +80,160,134871,1,0 +88,128,134975,2,0,B|104:88|144:56,1,90 +232,104,135389,6,0,B|256:152,2,45,0|0|8 +232,104,135803,1,0 +380,49,136010,1,0 +289,185,136217,1,0 +283,14,136424,1,0 +389,149,136631,1,0 +232,104,136838,1,0 +380,49,137045,1,0 +289,185,137252,5,4 +289,185,137459,1,0 +289,185,137563,1,0 +112,256,137873,1,0 +112,256,137977,1,0 +112,256,138080,2,0,B|160:272|208:256,1,90 +400,256,138494,2,0,B|352:240|304:256,1,90 +256,184,138908,5,8 +288,72,139115,1,0 +232,152,139322,1,0 +264,40,139529,1,0 +168,72,139736,1,0 +168,72,139943,1,0 +100,160,140150,1,0 +88,192,140254,1,0 +84,224,140357,1,0 +88,256,140461,1,0 +96,288,140565,6,0,B|128:328|184:336,1,90,4|0 +208,336,140875,1,0 +248,336,140979,2,0,B|336:336,1,90 +376,336,141289,1,0 +408,336,141393,1,0 +455,255,141600,1,0 +460,82,141807,1,0 +351,217,142014,1,0 +512,172,142221,5,8 +361,117,142428,1,0 +256,116,142635,2,0,B|208:116,3,45 +184,132,143049,2,0,B|144:160|144:216,1,90 +256,280,143463,2,0,B|296:252|296:196,1,90 +256,192,143877,12,4,147178 +152,120,148627,6,0,B|184:88,2,45 +152,232,149041,1,0 +264,232,149248,2,0,B|264:280|224:320,1,90 +152,232,149662,2,0,B|152:184|192:144,1,90 +288,128,150076,5,0 +416,64,150283,1,0 +288,128,150490,1,8 +392,184,150697,1,0 +392,184,150800,2,0,B|408:232|408:280|352:320,1,135 +367,307,151214,1,0 +367,307,151318,5,0 +256,344,151525,1,0 +98,316,151732,1,0 +126,158,151939,1,0 +283,186,152146,1,0 +194,290,152353,1,0 +36,262,152560,1,0 +64,104,152767,1,0 +221,132,152974,5,0 +194,290,153181,1,0 +128,192,153388,1,0 +144,192,153492,1,0 +160,192,153595,1,0 +176,192,153699,1,0 +192,192,153802,2,0,B|320:192,1,90,4|0 +280,192,154113,1,0 +472,152,154423,5,0 +480,192,154527,1,0 +472,232,154630,1,0 +336,192,154837,1,0 +456,112,155044,1,0 +456,272,155251,1,0 +360,328,155459,5,0 +152,328,155666,1,0 +256,272,155873,1,0 +256,240,155976,1,0 +256,208,156080,1,0 +256,176,156183,1,0 +256,144,156287,1,0 +320,64,156494,2,0,B|376:64|392:112,2,90,0|0|0 +216,64,157115,5,8 +216,64,157322,1,0 +216,64,157425,1,0 +88,104,157736,1,0 +88,104,157839,1,0 +88,104,157943,2,0,B|88:200,1,90 +184,256,158357,2,0,B|184:160,1,90 +88,228,158771,2,0,B|88:324,1,90 +88,318,159081,1,0 +88,318,159185,1,0 +192,320,159392,5,0 +208,320,159495,1,0 +224,320,159599,1,0 +240,320,159703,1,0 +256,320,159806,1,0 +272,320,159910,1,0 +288,320,160013,1,0 +304,320,160117,1,0 +320,320,160220,1,0 +336,320,160324,1,0 +352,320,160427,1,4 +152,72,173263,6,0,B|152:128,4,45,0|0|0|0|8 +248,48,173889,2,0,B|336:48,1,90,0|0 +408,120,174303,2,0,B|456:120,2,45,0|0|0 +376,152,174613,2,0,B|328:152,2,45 +408,184,174924,2,0,B|464:184,2,45 +376,216,175234,1,0 +344,240,175338,6,0,B|240:240,1,90,0|0 +184,184,175752,1,0 +144,296,175959,1,0 +152,160,176166,1,0 +112,272,176373,1,0 +32,216,176580,1,0 +32,112,176787,2,0,B|48:72|96:48,1,90,0|0 +200,48,177201,5,0 +200,48,177304,2,0,B|344:48,1,135 +368,48,177718,1,0 +400,48,177822,1,0 +448,144,178029,1,0 +424,256,178236,2,0,B|376:240|352:192,1,90,0|0 +344,160,178547,1,0 +336,120,178650,2,0,B|288:128|248:168,1,90,0|0 +160,224,179064,6,0,B|256:248,1,90 +352,160,179478,2,0,B|256:136,1,90 +176,88,179892,1,0 +144,72,179996,1,0 +112,88,180099,1,0 +80,72,180203,1,0 +56,88,180306,5,0 +56,176,180513,1,0 +56,176,180617,2,0,B|56:312,1,135 +56,311,181031,1,0 +56,311,181134,1,0 +168,312,181341,1,0 +256,256,181548,1,0 +344,312,181755,1,0 +440,280,181963,2,0,B|472:240|464:192,1,90,0|0 +440,96,182377,5,0 +432,80,182480,1,0 +424,64,182584,1,0 +416,48,182687,1,0 +408,32,182791,2,0,B|344:32|344:32|328:56,1,90 +288,136,183205,2,0,B|200:136,1,90 +128,64,183619,2,0,B|120:32|120:32|56:32,1,90 +32,32,183929,2,0,B|32:168,1,135 +32,200,184343,1,0 +32,232,184447,5,0 +128,272,184654,2,0,B|216:320,1,90 +328,272,185068,2,0,B|240:224,1,90 +168,160,185482,1,0 +176,120,185585,1,0 +208,96,185689,1,0 +320,80,185896,5,0 +352,88,185999,1,0 +376,112,186103,1,0 +384,152,186207,1,0 +376,184,186310,1,0 +344,208,186414,1,0 +312,208,186517,2,0,B|280:192,8,22.5,0|0|0|0|0|0|0|0|4 +256,192,187035,12,0,188173 +256,96,188380,5,0 +256,96,188484,1,0 +256,96,188587,1,8 +376,96,188794,1,0 +448,184,189001,2,0,B|360:216,1,90 +424,304,189415,2,0,B|336:336,1,90 +216,336,189829,1,0 +144,232,190036,1,0 +216,336,190244,1,4 +280,232,190451,5,0 +280,232,190554,2,0,B|280:96,1,135 +280,64,190968,1,0 +280,32,191072,1,0 +176,64,191279,1,0 +121,179,191486,1,0 +237,233,191693,1,0 +291,117,191900,2,0,B|360:184,1,90,8|0 +400,264,192314,6,0,B|352:256|304:264,1,90 +112,264,192728,2,0,B|160:256|208:264,1,90 +256,168,193142,2,0,B|280:128,4,45,0|0|0|0|4 +160,112,193763,1,0 +160,112,193866,2,0,B|104:112|64:144|64:208,1,135 +64,224,194280,1,0 +64,256,194384,1,0 +128,352,194591,5,0 +192,256,194798,1,0 +256,352,195005,1,0 +368,352,195212,2,0,B|416:340|420:280,1,90,8|0 +416,184,195626,1,0 +408,168,195730,1,0 +400,152,195833,1,0 +392,136,195937,1,0 +384,120,196040,5,0 +184,64,196247,1,0 +128,264,196454,1,0 +328,320,196661,1,0 +256,192,196868,12,8,199767 +256,192,200181,5,4 diff --git a/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu b/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu new file mode 100644 index 0000000000..4f8e8f820f --- /dev/null +++ b/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu @@ -0,0 +1,7102 @@ +osu file format v9 + +[General] +AudioFilename: Within Temptation - The Unforgiving.mp3 +AudioLeadIn: 2000 +PreviewTime: 2513029 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 0 +LetterboxInBreaks: 1 + +[Editor] +Bookmarks: 3177331 +DistanceSpacing: 0.9 +BeatDivisor: 4 +GridSize: 4 + +[Metadata] +Title:The Unforgiving +Artist:Within Temptation +Creator:Armin +Version:Marathon +Source: +Tags:Gonzvlo narakucrimson Roddie Vass_Bass ErufenRito Glass Card N'FoRcE force HakunoKaemi metal Why not me Shot in the Dark In the Middle of the Night Faster Fire and Ice Iron Where is the edge Sinead Lost Murder A Demon's Fate Stairway to the skies TU Marathon symphonic metal rock Sharon den Adel collab + +[Difficulty] +HPDrainRate:3 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:2 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"Within-Temptation.png" +Video,-300,"Mother Maiden_xvid_003.avi" +Video,910191,"Fire And Ice_x264.avi" +Video,648748,"17 Faster_x264_001.avi" +Video,1730578,"19 Sinead_xvid_001.avi" +Video,34667,"Within Temptation-Shot In The _x264_001.avi" +//Break Periods +2,31107,46554 +2,116273,123530 +2,193254,209002 +2,325697,336343 +2,442893,451543 +2,533893,549943 +2,644043,670986 +2,744936,750986 +2,886436,932774 +2,1017706,1021899 +2,1141547,1152290 +2,1285058,1290472 +2,1372331,1382290 +2,1476876,1505227 +2,1575917,1584318 +2,1666307,1676266 +2,1714619,1747493 +2,1825109,1829544 +2,1903058,1916980 +2,1935879,1949288 +2,1981007,1993647 +2,2205454,2225075 +2,2290311,2308242 +2,2394266,2400928 +2,2473072,2486674 +2,2513371,2525630 +2,2555908,2578758 +2,2686971,2696967 +2,2787469,2800847 +2,2889559,2901019 +2,2925673,2939538 +2,3002710,3007686 +2,3086784,3102501 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +2406,418.848167539267,4,2,0,100,1,0 +15829,418.848167539267,4,2,0,100,1,0 +34572,565.904267861353,4,1,0,100,1,0 +47011,-100,4,2,1,100,0,0 +95696,566.037735849057,4,2,1,100,1,0 +97111,-100,4,1,0,80,0,1 +97247,-100,4,1,0,82,0,1 +97388,-100,4,1,0,84,0,1 +97530,-100,4,1,0,86,0,1 +97671,-100,4,1,0,90,0,1 +97818,-100,4,1,0,93,0,0 +97960,-100,4,1,0,96,0,1 +98096,-100,4,2,1,100,0,1 +106020,-100,4,1,1,80,0,1 +106233,-100,4,1,1,83,0,1 +106374,-100,4,1,1,86,0,1 +106516,-100,4,1,1,89,0,1 +106657,-100,4,1,1,92,0,1 +106799,-100,4,1,1,95,0,1 +106940,-100,4,2,1,98,0,1 +116073,-100,4,2,1,98,0,0 +124134,-100,4,1,0,100,0,0 +124983,-100,4,2,1,100,0,0 +160926,-100,4,1,0,100,0,0 +161209,-100,4,2,1,100,0,0 +165454,-100,4,1,0,100,0,0 +165737,-100,4,2,1,100,0,0 +169983,-100,4,1,0,100,0,0 +170266,-100,4,2,0,87,0,0 +172671,-100,4,1,0,100,0,0 +173101,-100,4,1,0,81,0,1 +173237,-100,4,1,0,84,0,1 +173379,-100,4,1,0,87,0,1 +173520,-100,4,1,0,90,0,1 +174658,-100,4,2,0,100,0,0 +174941,-100,4,2,1,100,0,1 +182436,-100,4,2,0,100,0,1 +183002,-100,4,1,0,100,0,1 +183714,-100,4,2,0,100,0,0 +183997,-100,4,2,1,100,0,1 +191563,-100,4,1,0,80,0,1 +191775,-100,4,1,0,83,0,1 +191917,-100,4,1,0,86,0,1 +192058,-100,4,1,0,89,0,1 +192200,-100,4,1,0,91,0,1 +192341,-100,4,1,0,93,0,1 +192483,-100,4,1,0,95,0,1 +192624,-100,4,1,0,97,0,1 +192766,-100,4,1,0,98,0,1 +192907,-100,4,1,0,99,0,1 +193054,-100,4,2,0,100,0,0 +209888,-100,4,1,0,63,0,0 +211311,-100,4,2,0,5,0,0 +214708,-100,4,2,0,30,0,0 +215549,-100,4,1,0,15,0,0 +215686,566.037735849057,4,1,0,15,1,0 +215761,-100,4,2,0,5,0,0 +217105,-100,4,2,0,31,0,0 +217884,-100,4,1,0,15,0,0 +218450,-100,4,2,0,30,0,0 +218662,-100,4,1,0,15,0,0 +219299,-100,4,2,0,29,0,0 +219511,-100,4,2,0,36,0,0 +220148,-100,4,1,0,16,0,0 +220289,-100,4,2,0,16,0,0 +220997,-100,4,1,0,16,0,0 +221138,-100,4,2,0,16,0,0 +222341,-100,4,1,0,16,0,0 +222907,-100,4,2,0,16,0,0 +223190,-100,4,1,0,16,0,0 +223473,-100,4,2,0,16,0,0 +224605,-100,4,1,0,16,0,0 +225171,-100,4,2,0,16,0,0 +225454,-100,4,1,0,16,0,0 +226091,-100,4,2,0,16,0,0 +226870,-100,4,1,0,16,0,0 +227436,-100,4,2,0,16,0,0 +227719,-100,4,1,0,16,0,0 +228072,-100,4,2,0,16,0,0 +229134,-100,4,1,0,16,0,0 +229770,-100,4,2,0,16,0,0 +229983,-100,4,1,0,16,0,0 +230195,-100,4,2,0,16,0,0 +231469,-100,4,1,0,16,0,0 +231610,-100,4,2,0,16,0,0 +232318,-100,4,1,0,16,0,0 +232601,-100,4,2,0,33,0,0 +233167,-100,4,2,0,21,0,0 +233733,-100,4,1,0,21,0,0 +243355,-100,4,1,0,41,0,0 +246256,-100,4,1,0,26,0,0 +247954,-100,4,1,0,39,0,0 +250510,-100,4,2,0,40,0,0 +250651,-100,4,2,0,50,0,0 +250793,-100,4,2,0,60,0,0 +251912,-100,4,2,1,100,0,1 +267761,-100,4,2,1,100,0,0 +270025,-100,4,2,1,100,0,1 +270171,-76.9230769230769,4,2,1,83,0,1 +285737,-76.9230769230769,4,1,0,36,0,1 +286162,-76.9230769230769,4,1,0,45,0,1 +286445,-76.9230769230769,4,1,0,52,0,1 +286728,-76.9230769230769,4,1,0,60,0,1 +287011,-76.9230769230769,4,1,0,68,0,1 +287294,-76.9230769230769,4,1,0,76,0,1 +287577,-76.9230769230769,4,1,1,83,0,1 +287855,-76.9230769230769,4,2,1,100,0,0 +288138,-76.9230769230769,4,2,1,100,0,1 +296068,-76.9230769230769,4,1,1,54,0,1 +296351,-76.9230769230769,4,1,1,60,0,1 +296634,-76.9230769230769,4,1,1,71,0,1 +296923,-76.9230769230769,4,2,1,93,0,0 +297195,-76.9230769230769,4,2,1,100,0,1 +306252,-76.9230769230769,4,2,1,100,0,0 +324082,-76.9230769230769,4,2,1,100,0,0 +324152,-76.9230769230769,4,2,1,65,0,0 +337093,300,4,2,1,40,1,0 +346663,-100,4,1,0,70,0,0 +346851,-100,4,2,1,70,0,0 +366193,-100,4,2,1,70,0,0 +384870,-100,4,2,0,70,0,0 +384943,-100,4,1,0,70,0,0 +385138,-100,4,2,1,70,0,0 +388663,-100,4,1,0,70,0,0 +389001,-100,4,2,1,70,0,0 +399493,-100,4,1,0,70,0,0 +399651,-100,4,2,1,70,0,0 +408343,-100,4,1,0,70,0,0 +408943,-100,4,1,0,70,0,0 +409093,-100,4,1,0,70,0,0 +409168,-100,4,2,1,70,0,0 +416593,-100,4,1,0,70,0,0 +416670,-100,4,2,0,70,0,0 +416743,-100,4,1,0,50,0,0 +416893,-100,4,1,0,80,0,0 +416968,-100,4,2,0,70,0,0 +417043,-100,4,1,0,50,0,0 +417193,-100,4,1,0,90,0,0 +417270,-100,4,2,0,70,0,0 +417493,-100,4,1,0,100,0,0 +417568,-100,4,2,0,70,0,0 +418693,-100,4,2,1,70,0,1 +428226,-100,4,1,0,70,0,1 +428451,-100,4,2,1,70,0,1 +436634,-100,4,2,0,70,0,1 +436784,-100,4,1,0,70,0,1 +437830,-100,4,1,0,70,0,0 +437893,-100,4,1,0,70,0,1 +442693,-100,4,1,0,70,0,0 +452001,-100,4,2,1,70,0,0 +461293,-100,4,1,0,70,0,0 +461976,-100,4,2,1,70,0,0 +470293,-100,4,1,0,70,0,0 +471576,-100,4,2,0,70,0,0 +471726,-100,4,1,0,70,0,0 +471876,-100,4,2,1,70,0,0 +475693,-100,4,1,0,70,0,0 +475926,-100,4,1,0,70,0,0 +476526,-100,4,1,0,70,0,0 +476676,-100,4,2,1,70,0,0 +479893,-100,4,1,0,70,0,0 +481176,-100,4,2,0,70,0,0 +481326,-100,4,1,0,70,0,0 +481476,-100,4,2,1,70,0,0 +490693,-100,4,1,0,70,0,1 +490851,-100,4,2,1,70,0,1 +499693,-100,4,1,0,60,0,1 +499993,-100,4,1,0,65,0,1 +500143,-100,4,1,0,70,0,0 +500293,-100,4,1,0,70,0,1 +501276,-100,4,2,0,70,0,1 +501426,-100,4,1,0,70,0,1 +501876,-100,4,2,0,70,0,1 +502326,-100,4,1,0,70,0,1 +502776,-100,4,2,0,70,0,1 +502926,-100,4,1,0,70,0,1 +509293,-100,4,1,0,60,0,1 +509593,-100,4,1,0,65,0,1 +514693,-100,4,1,0,70,0,0 +514709,-100,4,1,0,70,0,0 +563151,-83.3333333333333,4,1,0,70,0,0 +574251,-76.9230769230769,4,1,0,70,0,0 +581893,-76.9230769230769,4,1,0,70,0,1 +591193,-76.9230769230769,4,1,0,70,0,0 +591493,-76.9230769230769,4,1,0,70,0,1 +601093,-76.9230769230769,4,1,0,70,0,1 +603726,-83.3333333333333,4,1,0,70,0,1 +605893,-83.3333333333333,4,1,0,70,0,0 +634993,-83.3333333333333,4,1,0,20,0,0 +648736,500,4,2,1,100,1,0 +710611,-100,4,1,0,63,0,0 +710781,-100,4,1,0,65,0,0 +710857,-100,4,1,0,70,0,0 +710982,-100,4,1,0,76,0,0 +711107,-100,4,1,0,79,0,0 +711232,-100,4,1,0,85,0,0 +711357,-100,4,1,0,91,0,0 +711482,-100,4,1,0,94,0,0 +711781,-100,4,1,0,100,0,0 +712736,-76.9230769230769,4,1,0,100,0,1 +712798,-76.9230769230769,4,2,1,100,0,1 +720498,-76.9230769230769,4,1,0,100,0,0 +720736,-76.9230769230769,4,1,0,100,0,1 +721986,-76.9230769230769,4,2,1,100,0,1 +727986,-76.9230769230769,4,1,0,80,0,1 +728111,-76.9230769230769,4,1,0,83,0,1 +728236,-76.9230769230769,4,1,0,86,0,1 +728361,-76.9230769230769,4,1,0,89,0,1 +728486,-76.9230769230769,4,1,0,92,0,1 +728623,-76.9230769230769,4,1,0,95,0,0 +728736,-76.9230769230769,4,1,0,98,0,1 +744736,-100,4,1,0,100,0,0 +751234,-100,4,2,1,100,0,0 +752611,-100,4,1,0,100,0,0 +752861,-100,4,2,1,100,0,0 +759611,-100,4,1,0,100,0,0 +759861,-100,4,2,0,100,0,0 +760173,-100,4,1,0,100,0,0 +760361,-100,4,2,0,100,0,0 +760673,-100,4,1,0,100,0,0 +760861,-100,4,2,1,100,0,0 +784548,-100,4,2,0,100,0,0 +785111,-100,4,1,0,100,0,0 +785361,-83.3333333333333,4,1,0,100,0,0 +785548,-83.3333333333333,4,2,0,100,0,0 +786111,-83.3333333333333,4,1,0,100,0,0 +786361,-71.4285714285714,4,1,0,100,0,0 +786548,-71.4285714285714,4,2,0,100,0,0 +787111,-62.5,4,1,0,100,0,0 +787548,-62.5,4,2,0,100,0,0 +788111,-55.5555555555556,4,1,0,100,0,0 +788548,-55.5555555555556,4,2,0,100,0,0 +789111,-55.5555555555556,4,1,0,100,0,0 +789361,-50,4,1,0,100,0,0 +789548,-50,4,2,0,100,0,0 +790111,-76.9230769230769,4,1,0,100,0,0 +790736,-76.9230769230769,4,1,0,80,0,0 +790986,-76.9230769230769,4,1,0,85,0,0 +791236,-76.9230769230769,4,1,0,90,0,0 +791486,-76.9230769230769,4,1,0,95,0,0 +791736,-76.9230769230769,4,1,0,100,0,0 +792736,-76.9230769230769,4,1,0,100,0,1 +800486,-76.9230769230769,4,1,0,100,0,0 +800736,-76.9230769230769,4,1,0,100,0,1 +808611,-76.9230769230769,4,1,0,100,0,0 +808736,-76.9230769230769,4,1,0,100,0,1 +824611,-76.9230769230769,4,1,0,100,0,0 +824736,-76.9230769230769,4,1,0,100,0,1 +824861,-76.9230769230769,4,1,0,100,0,0 +852361,-76.9230769230769,4,2,0,100,0,0 +858736,-76.9230769230769,4,1,0,100,0,0 +860736,-66.6666666666667,4,1,0,100,0,1 +868486,-66.6666666666667,4,1,0,100,0,0 +868736,-66.6666666666667,4,1,0,100,0,1 +872361,-66.6666666666667,4,1,0,100,0,1 +873861,-66.6666666666667,4,1,0,100,0,1 +884486,-66.6666666666667,4,1,0,100,0,0 +884736,-66.6666666666667,4,1,0,100,0,1 +886236,-66.6666666666667,4,1,0,100,0,0 +912957,856.959223023638,4,2,0,21,1,0 +941887,-117.647058823529,4,2,0,21,0,0 +972298,-100,4,2,0,21,0,0 +974654,-100,4,2,0,21,0,0 +991894,-117.647058823529,4,2,0,21,0,0 +992858,-100,4,2,0,21,0,0 +1015131,-90.9090909090909,4,2,0,21,0,0 +1015452,-90.9090909090909,4,2,0,5,0,0 +1017701,-100,4,2,0,15,0,0 +1022649,428.571428571429,4,2,1,78,1,0 +1030031,-90.9090909090909,4,2,0,79,0,0 +1031103,-100,4,2,1,79,0,0 +1049960,-100,4,1,0,60,0,0 +1050174,-100,4,2,0,100,0,0 +1051674,-100,4,1,0,60,0,0 +1051889,-100,4,2,0,100,0,0 +1052103,-100,4,1,0,60,0,0 +1052317,-100,4,2,0,100,0,0 +1052531,-100,4,1,0,60,0,0 +1052746,-100,4,2,0,100,0,0 +1052960,-100,4,1,0,60,0,0 +1053174,-100,4,2,0,100,0,0 +1053389,-100,4,1,0,80,0,0 +1053603,-100,4,2,0,100,0,0 +1054246,-100,4,1,0,80,0,0 +1054460,-100,4,2,1,81,0,0 +1067103,-100,4,1,0,80,0,0 +1067317,-100,4,2,1,100,0,0 +1069674,-100,4,1,0,85,0,0 +1069996,-100,4,2,1,85,0,0 +1070531,-100,4,1,0,68,0,0 +1070746,-100,4,2,0,100,0,0 +1071389,-100,4,1,0,80,0,0 +1071603,-100,4,2,0,100,0,0 +1072246,-100,4,1,0,60,0,0 +1072460,-100,4,2,0,100,0,0 +1073103,-100,4,1,0,80,0,0 +1073317,-100,4,2,0,100,0,0 +1073960,-100,4,1,0,60,0,0 +1074174,-100,4,2,0,100,0,0 +1074389,-100,4,1,0,60,0,0 +1074603,-100,4,2,0,100,0,0 +1074817,-100,4,1,0,80,0,0 +1075031,-100,4,2,0,100,0,0 +1075674,-100,4,1,0,60,0,0 +1076317,-100,4,2,0,100,0,0 +1076531,-100,4,1,0,80,0,0 +1076746,-100,4,2,0,100,0,0 +1077389,-100,4,2,1,81,0,0 +1081246,-90.9090909090909,4,2,1,82,0,0 +1082317,-100,4,2,1,80,0,0 +1083281,-100,4,2,1,80,0,0 +1093460,-90.9090909090909,4,2,1,80,0,0 +1094317,-100,4,2,1,80,0,0 +1105674,-100,4,2,1,80,0,0 +1118531,-100,4,1,0,80,0,0 +1118746,-100,4,2,1,80,0,0 +1122014,-100,4,1,0,80,0,0 +1122174,-100,4,2,1,80,0,0 +1128817,-133.333333333333,4,2,1,80,0,0 +1135674,-100,4,2,0,22,0,0 +1137505,444.444444444444,4,2,0,22,1,0 +1139282,461.538461538462,4,2,0,16,1,0 +1140205,571.428571428571,4,2,0,19,1,0 +1141347,571.428571428571,4,2,0,19,1,0 +1150313,454.545454545455,4,2,0,100,1,0 +1153040,-100,4,2,1,100,0,0 +1226981,-72.992700729927,4,2,1,100,0,0 +1241222,-68.0272108843537,4,2,1,100,0,1 +1284868,-105.042016806723,4,1,0,100,0,0 +1291224,-80.7754442649435,4,1,0,100,0,0 +1291299,-80.7754442649435,4,2,1,100,0,0 +1291375,-80.7754442649435,4,1,0,100,0,0 +1291451,-80.7754442649435,4,2,1,100,0,0 +1291527,-80.7754442649435,4,1,0,100,0,0 +1291602,-80.7754442649435,4,2,1,100,0,0 +1291678,-74.6268656716418,4,1,0,100,0,0 +1292133,-72.7272727272727,4,2,1,100,0,0 +1328494,-72.992700729927,4,2,1,100,0,1 +1342147,-72.992700729927,4,2,1,100,0,1 +1342737,-72.992700729927,4,2,1,100,0,0 +1343040,-72.992700729927,4,2,1,100,0,1 +1345025,-63.2911392405063,4,2,1,100,0,1 +1345935,-72.992700729927,4,2,1,100,0,1 +1357585,-72.992700729927,4,2,1,100,0,0 +1372139,-72.992700729927,4,2,1,100,0,0 +1408427,-86.2068965517241,4,2,1,100,0,0 +1409571,-87.5656742556918,4,2,1,100,0,0 +1412071,-87.5656742556918,4,1,0,100,0,0 +1412222,-87.5656742556918,4,2,1,100,0,0 +1415767,-72.992700729927,4,2,1,100,0,1 +1430009,-72.992700729927,4,2,1,100,0,0 +1430312,-72.992700729927,4,2,1,100,0,1 +1430632,-63.2911392405063,4,2,1,100,0,1 +1444706,-63.2911392405063,4,2,1,100,0,0 +1444858,-63.2911392405063,4,2,1,100,0,1 +1473949,-63.2911392405063,4,2,1,100,0,0 +1491562,389.61038961039,4,1,0,80,1,0 +1506367,-200,4,1,0,80,0,0 +1507146,-100,4,1,0,80,0,0 +1550782,-100,4,1,0,80,0,1 +1575717,-100,4,1,0,80,0,0 +1584873,-100,4,2,0,70,0,0 +1587511,-100,4,1,0,80,0,0 +1589938,-200,4,1,0,80,0,0 +1590523,-100,4,1,0,80,0,0 +1619354,-100,4,1,0,80,0,1 +1644289,-100,4,1,0,80,0,0 +1662990,-100,4,2,0,40,0,0 +1678575,-100,4,2,0,70,0,0 +1688315,-100,4,1,0,40,0,0 +1688510,-100,4,1,0,50,0,0 +1688704,-100,4,1,0,60,0,0 +1688899,-100,4,1,0,70,0,0 +1689094,-100,4,1,0,80,0,0 +1689289,-100,4,1,0,80,0,0 +1689484,-100,4,1,0,80,0,0 +1691042,-100,4,1,0,80,0,1 +1714419,-100,4,2,0,40,0,0 +1730551,512.820512820513,4,1,0,100,1,0 +1747667,-181.818181818182,4,2,0,35,0,0 +1763137,-120.481927710843,4,2,1,70,0,0 +1795191,-120.481927710843,4,2,1,90,0,0 +1796192,-90.9090909090909,4,2,1,100,0,1 +1824909,-100,4,2,1,100,0,0 +1830166,-119.760479041916,4,2,1,100,0,0 +1874140,-90.9090909090909,4,2,1,100,0,1 +1902858,-100,4,2,1,100,0,0 +1917477,-120.481927710843,4,2,1,100,0,0 +1950551,-120.481927710843,4,2,1,70,0,0 +1951063,-120.481927710843,4,2,1,60,0,0 +1952088,-90.9090909090909,4,2,1,100,0,1 +1967986,-90.9090909090909,4,2,1,100,0,0 +1968499,-90.9090909090909,4,2,1,100,0,1 +1980807,-90.9090909090909,4,2,1,100,0,0 +1994397,428.571428571429,3,2,0,30,1,0 +1995484,-100,3,2,0,48,0,0 +1995913,-100,3,2,0,30,0,0 +2000627,-100,3,2,0,51,0,0 +2001055,-100,3,2,0,30,0,0 +2045825,-100,3,2,0,40,0,0 +2070163,-100,3,1,0,60,0,0 +2070323,-100,3,2,0,60,0,0 +2071539,-100,3,1,0,50,0,0 +2072825,-100,3,1,0,60,0,0 +2073055,-100,3,2,0,60,0,0 +2073913,-100,3,2,1,60,0,0 +2130682,-100,3,2,1,60,0,1 +2141198,-87.719298245614,3,2,1,60,0,1 +2142484,-100,3,2,1,60,0,1 +2153825,-100,3,2,1,60,0,0 +2189734,-87.719298245614,3,2,1,60,0,0 +2197448,-100,3,2,1,60,0,0 +2245012,-100,3,1,0,60,0,0 +2246397,-100,3,1,0,60,0,1 +2246619,-100,3,2,1,60,0,1 +2269111,-100,3,2,1,60,0,0 +2269539,-100,3,2,1,60,0,1 +2290111,-100,3,2,1,60,0,0 +2308992,447.761194029851,4,2,0,29,1,0 +2316044,-100,4,2,1,51,0,0 +2336305,-100,4,1,0,41,0,0 +2337318,-100,4,2,1,51,0,0 +2350969,-100,4,1,0,41,0,0 +2351865,-133.333333333333,4,2,1,60,0,0 +2365521,-133.333333333333,4,1,0,20,0,0 +2365745,-133.333333333333,4,1,0,30,0,0 +2365969,-100,4,1,0,50,0,0 +2366305,-100,4,1,2,60,0,1 +2379408,-100,4,1,2,70,0,1 +2381087,-100,4,1,2,60,0,1 +2394962,-100,4,1,2,60,0,0 +2402350,-100,4,2,1,55,0,0 +2415223,-100,4,1,0,50,0,0 +2416566,-100,4,2,1,50,0,0 +2429775,-100,4,1,0,50,0,0 +2430671,-133.333333333333,4,2,1,50,0,0 +2444327,-133.333333333333,4,2,1,40,0,0 +2444551,-133.333333333333,4,2,1,50,0,0 +2445111,-100,4,1,2,60,0,1 +2470186,-100,4,1,0,65,0,0 +2488096,-100,4,1,0,65,0,1 +2488214,-100,4,1,0,65,0,0 +2502424,-100,4,1,0,65,0,1 +2502542,-100,4,1,0,65,0,0 +2526162,-100,4,1,0,40,0,0 +2526721,-100,4,1,0,50,0,0 +2527169,-100,4,1,0,60,0,0 +2527499,-100,4,1,2,65,0,1 +2541380,-100,4,1,2,65,0,0 +2541827,-100,4,1,2,65,0,1 +2553021,-100,4,1,2,65,0,1 +2555596,-100,4,1,2,5,0,1 +2556156,-100,4,1,2,56,0,0 +2565180,447.761194029851,4,1,0,100,1,0 +2579508,-100,4,2,0,80,0,0 +2586112,-100,4,1,0,69,0,0 +2643985,-100,4,1,0,69,0,1 +2658314,-100,4,1,0,69,0,1 +2671747,-100,4,1,0,69,0,1 +2672642,-100,4,1,0,69,0,0 +2701539,-90.9090909090909,4,1,0,69,0,0 +2715643,-100,4,1,0,69,0,0 +2729956,-100,4,1,0,69,0,1 +2758612,-76.9230769230769,4,1,0,69,0,0 +2761875,-66.6666666666667,4,1,0,69,0,0 +2801605,-133.333333333333,4,1,0,60,0,0 +2815926,-100,4,1,0,60,0,0 +2844582,-100,4,1,0,60,0,1 +2855329,-100,4,1,0,60,0,0 +2858911,-100,4,1,0,60,0,1 +2873239,-100,4,1,0,60,0,0 +2889304,-100,4,2,0,5,0,0 +2895844,370.37037037037,4,2,0,100,1,0 +2901399,-166.666666666667,4,2,0,50,0,0 +2924269,-200,4,2,0,50,0,0 +2940108,-200,4,2,1,50,0,0 +2952145,-100,4,2,1,75,0,0 +2960479,-133.333333333333,4,2,1,75,0,0 +2962331,-100,4,2,1,75,0,0 +2976220,-100,4,2,1,75,0,0 +3016775,-133.333333333333,4,2,1,75,0,0 +3018627,-100,4,2,1,75,0,0 +3057232,-151.515151515152,4,1,0,60,0,0 +3058719,-100,4,2,0,75,0,0 +3059182,-100,4,2,1,75,0,0 +3082562,-100,4,2,0,10,0,0 +3086775,-100,4,2,0,75,0,0 +3103066,-151.515151515152,4,1,0,60,0,0 +3105571,-151.515151515152,4,2,0,75,0,0 +3106126,-151.515151515152,4,1,0,60,0,0 +3108066,-151.515151515152,4,2,0,75,0,0 +3108437,-151.515151515152,4,1,0,60,0,0 +3108626,-151.515151515152,4,2,0,75,0,0 +3109177,-151.515151515152,4,1,0,60,0,0 +3111029,-151.515151515152,4,2,0,75,0,0 +3111400,-151.515151515152,4,1,0,60,0,0 +3111589,-151.515151515152,4,2,0,75,0,0 +3112140,-151.515151515152,4,1,0,60,0,0 +3117978,-100,4,2,1,75,0,0 +3126214,-100,4,2,1,75,0,0 +3165474,-100,4,2,1,75,0,1 +3176960,-100,4,2,1,75,0,0 +3177325,-100,4,2,1,75,0,1 +3177695,-100,4,2,1,75,0,0 + + +[Colours] +Combo1 : 12,50,233 +Combo2 : 253,225,28 +Combo3 : 185,17,17 +Combo4 : 30,136,35 +SliderBorder : 0,0,0 + +[HitObjects] +40,48,2405,1,4 +144,344,2824,1,0 +256,48,3243,1,2 +368,344,3662,1,0 +472,48,4081,1,2 +336,216,4500,2,0,B|308:146|192:136|171:225|171:225,1,200,0|2 +256,256,5128,1,0 +256,336,5337,1,0 +92,88,5756,5,2 +256,144,6175,1,0 +420,88,6594,1,2 +400,252,7013,1,0 +256,336,7432,1,2 +112,252,7851,1,0 +92,88,8269,1,2 +336,172,8688,6,0,B|308:102|192:92|171:181|171:181,1,200,0|2 +348,348,9526,1,0 +164,348,9945,1,2 +80,160,10364,1,0 +256,40,10782,1,2 +432,160,11201,1,0 +256,196,11620,1,2 +256,196,11830,1,0 +256,196,12039,1,0 +176,316,12458,6,0,B|204:386|320:396|341:307|341:307,1,200,2|0 +80,224,13296,1,2 +256,44,13714,1,0 +432,224,14133,1,2 +112,92,14552,1,0 +256,272,14971,1,2 +400,92,15390,1,0 +456,336,15829,5,4 +256,44,16247,1,0 +56,336,16666,1,2 +256,272,17085,1,0 +88,68,17504,1,2 +424,68,17923,1,0 +168,208,18342,1,2 +256,168,18551,1,0 +344,208,18760,1,0 +176,312,19179,6,0,B|204:382|320:392|341:303|341:303,1,200,2|0 +472,192,20017,1,2 +256,40,20436,1,0 +40,192,20855,1,2 +256,348,21274,1,0 +170,80,21692,2,0,B|179:110|211:135|252:141|252:141|280:151|302:184|280:217|256:223|227:217|199:184|225:151|252:141|252:141|296:135|323:114|337:72,1,400,2|2 +432,288,22949,5,0 +256,352,23368,1,2 +80,288,23787,1,0 +164,159,24205,2,0,B|208:128|256:168|256:168|304:128|348:158|348:158,1,200,2|0 +180,36,25043,1,2 +256,60,25253,1,0 +332,36,25462,1,0 +472,120,25881,6,0,B|402:148|392:264|481:285|481:285,1,200,2|0 +336,372,26719,2,0,B|308:302|192:292|171:381|171:381,1,200,2|0 +40,280,27556,2,0,B|110:252|120:136|31:115|31:115,1,200,2|0 +180,68,28394,5,2 +256,20,28603,1,0 +332,68,28813,1,0 +302,153,29022,1,0 +208,152,29232,1,0 +256,192,29336,12,0,30907 +40,116,47304,5,2 +100,44,47587,1,0 +172,104,47870,2,0,B|200:174|316:184|337:95|337:95,1,200,0|2 +412,44,48719,1,0 +472,116,49002,1,0 +428,304,49568,5,2 +354,355,49851,1,0 +342,265,50134,1,0 +168,265,50700,1,2 +156,355,50983,1,0 +82,304,51266,1,0 +212,171,51832,6,0,B|228:191|256:195|256:195|284:191|300:171,1,100,2|0 +333,86,52397,2,0,B|312:44|256:28|256:28|204:40|177:87,1,200,0|0 +256,112,53246,1,2 +255,112,53529,1,0 +340,252,54095,2,0,B|360:348|360:348,1,100,2|0 +340,252,54661,2,0,B|312:322|196:332|175:243|175:243,1,200,0|2 +151,349,55510,2,0,B|152:348|180:252,1,100,0|0 +256,112,56359,5,2 +300,40,56642,1,0 +212,40,56925,1,0 +184,129,57208,2,0,B|190:177|240:200|272:200|324:184|329:130|329:130,1,200,0|2 +348,227,58057,2,0,B|314:260|257:270|201:259|164:228,1,200,0|0 +104,288,58905,5,2 +256,368,59471,1,0 +408,288,60037,1,2 +408,288,60320,1,0 +256,200,60886,5,0 +336,160,61169,2,0,B|308:95|199:74|172:168|172:168,1,200,2|0 +300,28,62301,1,2 +212,32,62584,1,0 +212,32,63150,1,0 +300,32,63433,1,2 +342,197,63999,6,0,B|325:162|279:159|255:183|255:183|230:158|184:162|169:197|169:197,1,200,0|2 +168,280,64847,2,0,B|185:315|232:318|256:294|256:294|280:318|326:315|344:280|344:280,1,200,0|2 +432,272,65696,5,0 +400,356,65979,1,0 +336,188,66545,1,2 +256,144,66828,1,8 +176,188,67111,1,0 +112,28,67677,5,2 +84,112,67960,1,8 +20,44,68243,1,0 +216,88,68809,2,0,B|229:52|285:46|295:91|295:91,1,100,2|8 +256,164,69375,1,0 +432,112,69941,5,2 +492,48,70223,1,8 +400,28,70506,1,0 +332,196,71072,5,2 +256,244,71355,1,8 +180,196,71638,1,0 +336,284,72204,2,0,B|328:340|376:372|376:372,1,100,2|8 +336,284,72770,2,0,B|308:354|192:364|171:275|171:275,1,200,0|2 +137,371,73619,2,0,B|136:372|185:344|175:284,1,100,8|0 +156,108,74468,5,2 +212,36,74751,1,8 +300,36,75034,1,0 +356,108,75317,1,0 +256,124,75883,5,8 +256,124,76165,1,0 +384,332,76731,1,0 +384,332,77014,1,8 +256,220,77580,5,0 +128,332,78146,1,8 +128,332,78429,1,0 +256,308,78995,5,0 +256,308,79278,1,8 +300,232,79561,1,0 +256,220,79702,1,0 +212,232,79844,1,0 +112,92,80410,1,8 +176,32,80693,2,0,B|202:104|314:115|334:26|334:26,1,200,0|0 +400,92,81542,1,8 +332,248,82107,2,0,B|307:287|271:282|271:282|255:302|255:302|239:282|239:282|203:290|178:248,1,200,0|8 +104,300,82956,5,0 +168,364,83239,1,2 +256,380,83522,1,0 +344,364,83805,1,8 +408,300,84088,1,0 +336,240,84371,6,4,B|310:312|198:323|178:234|178:234,1,200,2|8 +156,152,85220,2,0,B|396:152|396:152,1,200,2|0 +336,64,86069,2,0,B|310:-8|198:-19|178:70|178:70,1,200,8|2 +48,184,87201,6,0,B|8:284|8:284,1,100,8|0 +100,264,87767,2,0,B|124:360|124:360,1,100,2|0 +188,300,88332,2,0,B|276:360|276:360,1,100,8|0 +316,280,88898,5,2 +376,344,89464,1,8 +376,344,89747,1,0 +408,260,90313,1,0 +408,260,90596,1,8 +480,316,91162,1,2 +464,200,91728,6,0,B|504:100|504:100,1,100,8|0 +412,120,92294,2,0,B|388:24|388:24,1,100,2|0 +324,84,92860,2,0,B|236:24|236:24,1,100,8|0 +196,104,93426,1,2 +256,192,93567,12,0,95696 +448,352,96262,5,2 +376,296,96545,1,0 +304,352,96828,1,2 +240,288,97111,1,0 +200,272,97252,1,0 +184,232,97394,1,0 +200,192,97535,1,0 +240,176,97677,1,0 +280,192,97818,1,0 +296,232,97960,5,4 +376,200,98243,2,0,B|488:200|488:200,1,100,2|8 +512,120,98809,2,0,B|512:16|512:16,1,100,0|8 +432,64,99375,5,2 +360,8,99658,1,8 +310,88,99941,2,0,B|340:125|338:201|254:252|176:198|152:135|204:82|204:82,1,300,2|8 +152,8,101073,1,2 +80,64,101356,1,8 +16,128,101639,6,0,B|16:240|16:240,1,100,2|8 +96,272,102205,2,0,B|160:220|260:296|192:368|192:368,1,200,2|0 +288,384,103054,2,0,B|320:352|320:352|400:352|400:352,1,100,8|0 +447,299,103620,1,10 +363,264,103903,1,0 +305,199,104186,2,0,B|287:169|287:138|318:107|318:107|349:76|349:30|287:15|271:45|256:61|256:61|241:45|225:15|163:30|163:76|194:107|194:107|225:138|225:169|208:199,1,500,8|2 +112,208,105884,5,0 +40,160,106167,1,0 +24,200,106309,1,0 +32,248,106450,1,0 +56,280,106592,1,0 +96,296,106733,1,0 +144,292,106875,1,0 +184,272,107016,6,0,B|296:272|296:272,1,100,4|0 +328,352,107582,2,0,B|216:352|216:352,1,100,8|0 +376,272,108148,2,0,B|456:352|456:352,1,100,2|0 +488,264,108714,2,0,B|408:184|408:184,1,100,8|0 +328,196,109280,1,2 +256,144,109563,1,0 +184,196,109846,1,8 +160,108,110129,6,0,B|216:12|216:12,1,100,0|2 +301,25,110695,2,0,B|296:16|352:112,1,100,0|8 +256,144,111262,2,0,B|256:352|256:352,1,200,2|0 +344,368,112111,2,0,B|440:296|440:296,1,100,8|0 +168,368,112677,2,0,B|72:296|72:296,1,100,2|0 +177,242,113243,6,0,B|202:312|316:323|340:236|340:236,1,200,8|2 +256,200,114092,1,0 +320,136,114375,6,0,B|336:96|376:72|424:96|424:96,2,100,8|0|2 +256,72,115224,1,0 +192,136,115507,2,0,B|176:96|136:70|88:96|88:96,2,100,8|8|4 +172,64,124280,5,0 +256,36,124563,1,0 +340,64,124846,1,0 +256,224,125412,1,2 +256,136,125695,1,8 +332,184,125979,6,0,B|404:210|415:322|326:342|326:342,1,200,0|2 +256,376,126828,1,8 +180,340,127111,2,0,B|108:314|97:202|186:182|186:182,1,200,0|2 +256,136,127960,1,8 +256,224,128243,1,0 +380,84,128809,6,0,B|472:28,1,100,2|8 +476,120,129375,2,0,B|368:184|368:184|368:260,1,200,0|0 +456,296,130224,1,8 +384,348,130507,1,0 +128,348,131073,5,2 +56,296,131356,1,8 +144,258,131639,2,0,B|144:184|144:184|36:120,1,200,0|0 +46,32,132488,2,0,B|132:84,1,100,8|0 +256,196,133337,5,2 +300,124,133620,1,8 +212,124,133903,1,0 +184,213,134186,2,0,B|190:261|240:284|272:284|324:268|329:214|329:214,1,200,0|8 +256,196,135035,1,0 +412,284,135601,5,0 +412,284,135884,1,8 +256,372,136450,1,0 +100,284,137016,1,8 +100,284,137299,1,0 +28,229,137582,2,0,B|108:40|108:40,1,200,0|8 +484,163,138714,2,0,B|404:352|404:352,1,200,0|8 +316,348,139563,5,0 +196,44,140412,1,8 +256,196,140979,1,0 +352,96,141545,6,0,B|365:60|421:54|431:99|431:99,1,100,8|0 +296,260,142111,2,0,B|283:296|227:302|217:257|217:257,1,100,0|0 +84,96,142677,2,0,B|97:60|153:54|163:99|163:99,1,100,8|0 +336,176,143526,5,0 +256,132,143809,1,8 +176,176,144092,1,0 +80,328,144658,5,0 +148,268,144941,2,0,B|216:360|216:360,1,100,8|0 +432,328,145790,1,0 +364,268,146073,2,0,B|296:360|296:360,1,100,8|0 +260,182,146922,1,0 +256,264,147205,1,8 +260,180,147488,1,0 +80,56,148054,5,0 +148,116,148337,2,0,B|216:24|216:24,1,100,8|0 +432,56,149186,1,0 +364,116,149469,2,0,B|296:24|296:24,1,100,8|0 +444,204,150318,5,0 +420,292,150601,1,8 +344,340,150884,1,0 +168,340,151450,1,0 +92,292,151733,1,8 +68,204,152016,1,0 +256,216,152865,5,8 +256,216,153148,1,0 +360,92,153714,5,0 +300,24,153997,1,8 +212,24,154280,1,0 +152,92,154563,1,0 +211,267,155129,2,0,B|227:291|305:301|313:244|313:244,1,100,8|0 +256,188,155695,1,0 +165,339,156262,2,0,B|213:380|313:386|352:337|352:337,1,200,8|0 +256,188,157394,1,8 +200,128,157677,2,0,B|170:91|172:15|256:-36|334:18|358:81|306:134|306:134,1,300,0|8 +439,58,159092,5,0 +439,58,159658,5,8 +420,149,159941,1,0 +348,198,160224,1,8 +256,192,160507,1,0 +163,185,160790,1,8 +91,234,161073,1,0 +72,325,161356,1,4 +256,348,162205,5,0 +256,192,162771,5,0 +192,132,163054,1,8 +212,92,163195,1,0 +256,76,163337,1,0 +300,92,163479,1,0 +320,132,163620,1,0 +72,58,164186,5,8 +91,149,164469,1,0 +163,198,164752,1,8 +256,192,165035,1,0 +348,185,165318,1,8 +420,234,165601,1,0 +439,325,165884,1,8 +184,304,166733,6,0,B|190:352|240:375|272:375|324:359|329:305|329:305,1,200 +256,256,167582,1,8 +256,168,167865,1,0 +72,120,168714,5,8 +128,48,168997,1,0 +212,16,169280,1,8 +300,16,169563,1,0 +384,48,169846,1,8 +440,120,170129,1,0 +256,192,170412,12,0,172677 +36,64,173101,5,0 +36,64,173243,1,0 +36,64,173384,1,0 +36,64,173526,1,0 +96,132,173809,1,0 +168,80,174092,1,0 +212,64,174233,1,0 +252,80,174375,1,0 +264,124,174516,1,0 +288,160,174658,1,0 +332,168,174799,1,0 +372,148,174941,5,4 +428,76,175224,2,0,B|496:164|496:164,1,100,0|8 +433,225,175790,2,0,B|404:326|404:326,1,100,0|2 +320,292,176356,1,0 +272,368,176639,1,8 +196,320,176922,2,0,B|160:364|88:360|40:304|80:244|80:244,1,200 +112,168,177771,2,0,B|108:104|44:88|44:88,1,100,8|0 +128,32,178337,6,0,B|236:32|236:32,1,100,2|0 +272,112,178903,1,8 +356,80,179186,2,0,B|396:40|472:52|508:132|436:180|436:180,1,200 +384,232,180035,2,0,B|340:276|372:332|372:332,1,100,8|0 +284,360,180601,2,0,B|164:360,1,100,2|0 +124,300,181167,2,0,B|66:343|-4:276|33:216|72:200|72:200|105:183|173:149|86:10|12:108|12:108,1,400,8|8 +4,16,182582,5,0 +92,8,182865,1,2 +168,56,183148,1,0 +208,76,183290,1,0 +232,112,183431,1,0 +256,152,183573,1,0 +280,112,183714,1,0 +304,76,183856,1,0 +344,56,183997,1,4 +420,8,184280,1,0 +508,16,184563,1,8 +484,102,184846,6,0,B|424:92|391:141|391:141,1,100,0|2 +369,222,185412,2,0,B|439:321|439:321,1,100,0|8 +342,362,185979,2,0,B|316:322|256:308|200:320|168:364,1,200,2|0 +85,303,186828,2,0,B|73:321|143:222,1,100,8|0 +117,136,187394,2,0,B|121:141|88:92|28:102,1,100,2|0 +124,32,187960,5,8 +220,104,188243,2,0,B|199:79|201:28|257:-5|309:30|325:72|290:108|290:108,1,200,2|0 +388,32,189092,1,8 +460,100,189375,5,0 +392,172,189658,1,2 +468,236,189941,1,0 +398,303,190224,2,0,B|383:345|327:359|298:317|327:288|312:232|270:246|256:352|256:352|241:246|199:232|185:288|213:317|185:359|114:345|114:303,1,500,8|0 +68,308,191780,5,0 +26,288,191922,1,0 +16,245,192063,1,0 +40,207,192205,1,0 +81,189,192346,1,0 +110,154,192488,1,0 +125,114,192629,1,0 +104,73,192771,1,0 +62,54,192912,1,0 +21,72,193054,1,4 +256,40,209752,5,2 +144,96,210035,1,0 +184,112,210177,1,0 +212,148,210318,1,0 +216,192,210460,1,0 +256,212,210601,1,0 +296,192,210743,1,0 +300,148,210884,1,0 +328,112,211026,1,0 +368,96,211167,1,4 +256,192,211309,12,0,214563 +256,32,215129,5,2 +236,183,215686,2,0,B|172:199|130:270|147:356|224:381|284:386|309:374|391:320|389:233|322:176|264:182|264:182,1,500,0|0 +256,268,217384,1,0 +448,64,217950,5,0 +256,124,218516,1,0 +256,124,218799,1,0 +64,64,219365,1,0 +64,64,219648,1,0 +176,260,220214,2,0,B|204:195|313:174|340:268|340:268,1,200,0|0 +336,337,221063,2,0,B|308:402|199:423|172:329|172:329,1,200,0|0 +256,299,221912,1,0 +256,40,222478,5,0 +172,164,223044,1,0 +256,204,223327,1,0 +340,164,223610,1,0 +460,300,224176,5,0 +256,360,224742,1,0 +52,300,225308,1,0 +52,300,225591,5,0 +256,360,226157,1,0 +256,360,226440,1,0 +460,300,227006,1,0 +340,164,227572,5,0 +256,204,227855,1,0 +172,164,228138,1,0 +172,76,228421,1,0 +256,32,228704,1,0 +340,76,228987,1,0 +256,120,229270,5,0 +376,252,229836,1,0 +293,287,230119,2,0,B|314:312|312:363|256:396|204:361|188:319|223:283|223:283,1,200 +136,252,230969,1,0 +176,72,231535,2,0,B|204:137|313:158|340:64|340:64,1,200,0|0 +256,32,232384,1,0 +256,32,232667,5,2 +480,192,233233,1,2 +256,356,233799,1,0 +32,192,234365,1,0 +336,320,234931,6,0,B|308:255|199:234|172:328|172:328,1,200,0|0 +436,112,236063,2,0,B|371:140|350:249|444:276|444:276,1,200,0|0 +176,72,237195,2,0,B|204:137|313:158|340:64|340:64,1,200,0|0 +76,272,238327,2,0,B|141:244|162:135|68:108|68:108,1,200,0|0 +256,208,239459,5,0 +472,288,240025,1,0 +424,92,240591,1,0 +256,340,241157,1,0 +88,92,241723,1,0 +40,288,242289,1,0 +256,208,242855,1,0 +256,44,243421,5,0 +460,340,244553,1,0 +52,340,245685,1,0 +81,156,246252,6,0,B|81:80|139:80|196:80|196:156|196:156|196:233|254:233|311:233|311:156|311:156|311:80|368:80|437:80|427:164,1,600 +348,328,248516,2,0,B|300:369|200:375|161:326|161:326,1,200 +168,152,249648,2,0,B|256:200|256:200|364:140,1,200 +257,103,250497,2,0,B|256:64,3,33.3333333333333 +257,103,251063,1,0 +452,228,251912,6,0,B|376:320,1,100,4|0 +256,356,252478,2,0,B|256:236,1,100,8|0 +123,305,253044,2,0,B|60:228,1,100,0|0 +120,156,253610,1,8 +184,61,253893,2,0,B|190:109|240:132|272:132|324:116|329:62|329:62,1,200,0|0 +392,156,254742,1,8 +300,232,255025,5,0 +256,244,255167,1,0 +212,232,255308,1,0 +152,324,255591,1,0 +256,368,255874,1,8 +360,324,256157,1,0 +256,136,256723,5,0 +180,56,257006,2,0,B|120:156,1,100,8|0 +332,56,257572,2,0,B|392:156,1,100,0|0 +256,136,258138,1,8 +184,208,258421,2,0,B|204:248|240:250|256:258|256:258|272:268|280:288|268:302|256:302|256:302|240:302|230:290|240:268|256:260|256:260|268:250|304:252|328:208|328:208,1,300,0|8 +384,272,259553,5,0 +352,296,259695,1,0 +336,332,259836,1,0 +312,364,259978,1,0 +276,380,260119,1,0 +236,380,260261,1,0 +200,364,260402,1,8 +176,332,260544,1,0 +160,296,260685,1,0 +128,272,260827,1,0 +116,236,260969,6,0,B|48:156|137:48|228:96|255:150|255:150|282:196|338:291|530:151|381:54|381:54,1,600,4|8 +304,32,262950,6,0,B|276:16|240:16|240:16,3,50 +216,16,263516,2,0,B|192:28|140:20|140:20,3,50,0|0|8|0 +128,24,264082,2,0,B|100:36|72:76|72:76,3,50 +68,88,264648,2,0,B|68:120|44:152|44:152|48:152,3,50,0|0|8|0 +44,172,265214,2,0,B|32:200|40:248|40:248,3,50 +52,260,265780,2,0,B|64:288|64:324|64:324,3,50,0|0|8|0 +92,336,266346,2,0,B|104:356|144:372|144:372,3,50 +172,364,266912,2,0,B|208:348|248:364|248:364,3,50,0|0|8|0 +260,368,267478,2,0,B|288:372|328:356|328:356,2,50 +256,192,267902,12,4,270025 +128,184,270308,5,0 +148,72,270591,1,8 +256,32,270874,1,0 +364,72,271157,1,8 +384,184,271440,1,0 +256,152,271723,1,8 +201,261,272006,2,0,B|177:291|185:358|259:403|325:356|346:301|306:252|306:252,1,259.999995350838 +396,352,272855,5,8 +256,291,273138,1,0 +116,352,273421,1,8 +200,196,273704,2,0,B|228:163|314:147|339:225|339:225,1,129.999997675419,0|8 +376,112,274270,2,0,B|320:40|192:36|144:112|144:112,1,259.999995350838 +100,252,275119,6,0,B|204:352,1,129.999997675419,8|0 +318,342,275686,2,0,B|412:252,1,129.999997675419,8|0 +275,87,276252,2,0,B|305:44|435:126|257:242|257:242|80:146|188:33|251:98,1,519.999990701676,8|8 +412,252,277667,6,0,B|308:352,1,129.999997675419,0|8 +194,342,278233,2,0,B|100:252,1,129.999997675419,0|8 +230,87,278799,2,0,B|164:90|145:147|174:218|231:190|231:190|260:255|260:255|288:190|288:190|340:215|372:147|366:77|281:88|281:88,1,519.999990701676 +416,240,280214,5,8 +256,348,280497,1,0 +96,240,280780,1,8 +204,64,281063,2,0,B|180:94|188:161|262:206|328:159|349:104|309:55|309:55,1,259.999995350838,0|0 +416,240,281912,6,0,B|480:120,1,129.999997675419,8|0 +304,336,282478,2,0,B|300:305|268:290|247:290|214:300|211:335|211:335,1,129.999997675419,8|0 +34,125,283044,2,0,B|96:240,1,129.999997675419,8|0 +256,40,283893,5,0 +432,156,284176,1,8 +360,348,284459,1,0 +152,348,284742,1,8 +80,156,285025,1,0 +256,188,285308,1,8 +314,247,285874,5,0 +314,247,286016,1,0 +314,247,286157,1,0 +314,247,286299,1,0 +314,247,286440,1,0 +314,247,286582,1,0 +314,247,286723,1,0 +314,247,286865,1,0 +314,247,287006,1,0 +314,247,287148,1,0 +314,247,287289,1,0 +314,247,287431,1,0 +314,247,287572,1,0 +314,247,287714,1,0 +314,247,287855,1,0 +314,247,287997,1,0 +314,247,288138,1,4 +320,40,288421,6,0,B|184:40|184:40,1,129.999997675419,0|8 +420,256,288987,2,0,B|420:120|420:120,1,129.999997675419,0|8 +192,344,289553,2,0,B|328:344|328:344,1,129.999997675419,0|8 +92,128,290119,2,0,B|92:264|92:264,1,129.999997675419,0|8 +256,192,290686,1,0 +256,192,290827,1,0 +256,192,290969,1,8 +408,254,291252,6,0,B|312:350,1,129.999997675419,0|8 +316,38,291818,2,0,B|408:130,1,129.999997675419,0|8 +104,130,292384,2,0,B|195:38,1,129.999997675419,0|8 +195,345,292950,2,0,B|104:254,1,129.999997675419,0|8 +256,192,293516,1,0 +408,130,293799,6,0,B|312:34,1,129.999997675419,8|0 +316,345,294365,2,0,B|408:254,1,129.999997675419,8|0 +104,254,294931,2,0,B|195:345,1,129.999997675419,8|0 +195,38,295497,2,0,B|104:130,1,129.999997675419,8|0 +256,192,296063,5,0 +256,192,296204,1,0 +256,192,296346,1,0 +256,192,296487,1,0 +256,192,296629,1,0 +256,192,296770,1,0 +256,192,296912,1,0 +256,192,297053,1,0 +256,192,297195,1,4 +212,324,297478,6,0,B|215:354|247:369|267:369|301:358|304:324|304:324,1,129.999997675419,0|8 +52,248,298044,2,0,B|84:192|84:192|44:120|44:120,1,129.999997675419,0|8 +312,40,298610,2,0,B|256:72|256:72|184:32|184:32,1,129.999997675419,0|8 +460,248,299176,2,0,B|428:192|428:192|468:120|468:120,1,129.999997675419,0|8 +312,348,299742,6,0,B|256:316|256:316|184:356|184:356,1,129.999997675419,0|8 +80,148,300308,2,0,B|50:151|35:183|35:203|46:237|80:240|80:240,1,129.999997675419,0|8 +300,36,300874,2,0,B|297:66|265:81|245:81|211:70|208:36|208:36,1,129.999997675419,0|8 +432,240,301440,2,0,B|462:237|477:205|477:185|466:151|432:148|432:148,1,129.999997675419,0|8 +256,192,302006,5,0 +136,272,302289,2,0,B|110:298|74:278|74:278|94:314|69:338|69:338,1,129.999997675419,8|0 +256,344,302855,1,8 +376,272,303138,2,0,B|401:298|437:278|437:278|417:314|442:338|442:338,1,129.999997675419,0|8 +256,192,303704,5,0 +136,112,303987,2,0,B|110:86|74:106|74:106|94:70|69:46|69:46,1,129.999997675419,8|0 +256,40,304553,1,8 +376,112,304836,2,0,B|401:86|437:106|437:106|417:70|442:46|442:46,1,129.999997675419,0|8 +256,192,305402,6,0,B|296:256|296:256|360:256,1,129.999997675419,0|8 +256,192,305969,2,0,B|216:256|216:256|152:256,1,129.999997675419,0|12 +196,352,306535,2,0,B|232:352|232:352|256:336|256:336|280:352|280:352|328:352,1,129.999997675419,0|8 +416,256,307101,5,0 +444,120,307384,1,8 +348,40,307667,2,0,B|340:101|275:131|234:131|167:110|161:41|161:41,1,259.999995350838,0|0 +68,120,308516,1,8 +96,256,308799,1,0 +212,348,309082,6,0,B|215:318|247:303|267:303|301:314|304:348|304:348,1,129.999997675419,8|0 +320,192,309648,2,0,B|184:192,1,129.999997675419,8|0 +192,48,310214,2,0,B|256:68|256:68|320:48,1,129.999997675419,8|0 +372,176,310780,2,0,B|256:228|256:228|132:172,1,259.999995350838,12|8 +68,292,311629,1,0 +256,368,311912,1,8 +444,292,312195,1,0 +348,45,312761,6,0,B|341:106|277:136|236:136|169:115|163:46|163:46,1,259.999995350838,0|0 +82,161,313610,2,0,B|124:224|188:240|188:240,1,129.999997675419,8|0 +328,238,314176,2,0,B|388:216|430:161,2,129.999997675419,8|0|8 +256,356,315025,1,0 +256,356,315308,5,12 +328,240,315591,2,0,B|452:304|452:304,1,129.999997675419,0|8 +184,240,316157,2,0,B|60:304|60:304,1,129.999997675419,0|8 +151,91,316723,2,0,B|220:112|256:40|256:40|292:112|364:92,1,259.999995350838,0|0 +256,356,317855,5,0 +380,284,318138,1,8 +380,140,318421,1,0 +256,68,318704,1,8 +132,140,318987,1,0 +132,284,319270,1,8 +256,208,319553,1,0 +256,192,319695,12,0,320686 +256,192,320827,12,0,322101 +255,57,322950,5,0 +115,303,323233,1,8 +256,192,323516,1,0 +396,304,323799,1,8 +256,192,324082,6,0,B|256:292|256:292|312:292|380:264|392:164|368:108|332:60|244:56|164:52|120:144|136:200|120:264|244:292|244:292,1,649.999988377094,4|0 +32,64,337093,5,8 +48,64,337243,1,0 +64,64,337393,1,8 +256,92,337693,2,0,B|256:316|256:316,1,200,8|8 +432,352,338293,2,0,B|368:304|368:224|432:176|432:176,1,200,8|8 +82,178,338893,2,0,B|134:224|144:304|80:352,1,200,8|8 +124,339,339343,5,0 +167,327,339493,1,8 +210,313,339643,1,0 +253,301,339793,1,8 +293,102,340093,2,0,B|313:77|311:26|255:-7|203:28|187:70|222:106|222:106,1,200,8|8 +432,192,340693,1,8 +256,296,340993,1,8 +80,192,341293,1,8 +208,360,341593,5,8 +180,272,341743,1,0 +256,216,341893,1,8 +332,272,342043,1,0 +304,360,342193,1,8 +356,44,342493,6,0,B|152:44,1,200,8|8 +356,124,343093,2,0,B|144:124,2,200,8|8|8 +408,272,343993,1,8 +348,344,344143,1,0 +256,372,344293,1,8 +164,344,344443,1,0 +104,272,344593,1,8 +256,192,344893,1,8 +256,192,345043,12,4,346693 +344,36,346993,5,8 +164,36,347293,1,8 +216,171,347593,2,0,B|230:139|284:129|297:175|297:175,1,100,8|8 +329,245,347893,2,0,B|319:311|199:343|183:245|183:245,2,200,8|8|8 +172,356,348793,1,8 +256,384,348943,1,8 +340,356,349093,1,8 +344,56,349393,6,0,B|432:112|432:112|456:232,1,200,8|8 +168,56,349993,1,8 +168,56,350143,1,0 +168,56,350293,2,0,B|80:112|80:112|56:232,1,200,8|8 +348,328,350893,2,0,B|256:368|256:368|148:320,2,200,8|8|4 +256,172,351793,5,8 +388,48,352093,1,8 +256,172,352393,1,8 +256,260,352543,1,8 +256,172,352693,1,8 +124,48,352993,1,8 +124,238,353293,2,0,B|192:272|192:336|192:336|240:336|256:368|256:368|272:336|320:336|320:336|320:272|393:240,1,400,8|4 +256,40,354193,5,8 +76,340,354493,1,8 +436,340,354793,1,8 +376,272,354943,6,0,B|410:236|410:236|476:237,1,100,0|8 +462,146,355243,2,0,B|412:144|412:144|368:94,1,100,0|8 +296,148,355543,2,0,B|282:116|228:106|215:152|215:152,1,100,0|8 +132,106,355843,2,0,B|99:144|99:144|50:146,1,100,0|8 +51,237,356143,2,0,B|101:236|101:236|136:272,1,100,0|4 +216,236,356443,6,0,B|230:268|284:278|297:232|297:232,1,100,0|8 +332,316,356743,2,0,B|352:288|352:288|384:288|384:288|404:260,2,100,0|8|0 +256,360,357193,1,8 +176,316,357343,2,0,B|156:288|156:288|124:288|124:288|104:260,2,100,0|8|0 +212,232,357793,1,8 +300,232,357943,1,0 +340,152,358093,2,0,B|339:105|339:105|380:70,1,100,8|0 +299,18,358393,2,0,B|256:40|256:40|206:15,1,100,8|0 +132,70,358693,2,0,B|173:105|173:105|172:152,1,100,4|0 +96,200,358993,5,8 +20,248,359143,1,0 +68,324,359293,1,8 +144,276,359443,1,0 +212,340,359593,2,0,B|228:340|228:340|248:332|248:332|256:344|256:344|264:332|264:332|280:340|280:340|296:340,1,100,8|0 +368,276,359893,1,8 +444,324,360043,1,0 +492,248,360193,1,8 +416,200,360343,1,0 +344,144,360493,6,0,B|376:130|386:76|340:63|340:63,1,100,8|0 +256,36,360793,1,8 +168,64,360943,2,0,B|136:78|126:132|172:145|172:145,1,100,0|4 +200,228,361243,1,0 +148,300,361393,2,0,B|100:280|100:280|88:232|88:232,2,100,8|0|8 +211,364,361843,2,0,B|256:341|256:341|306:366,1,100,0|8 +364,300,362143,2,0,B|412:280|412:280|424:232|424:232,2,100,0|8|0 +312,228,362593,1,8 +304,140,362743,5,0 +332,56,362893,1,8 +256,4,363043,1,0 +180,56,363193,1,8 +208,140,363343,1,0 +200,228,363493,5,4 +116,192,363643,1,0 +52,252,363793,1,8 +92,336,363943,1,0 +184,320,364093,1,8 +256,372,364243,1,0 +328,320,364393,1,8 +420,336,364543,1,8 +460,252,364693,1,8 +396,192,364843,1,8 +312,228,364993,1,8 +294,139,365143,6,0,B|280:171|226:181|213:135|213:135,2,100,8|8|8 +295,49,365593,2,0,B|281:17|227:7|214:53|214:53,2,100,8|8|4 +416,181,366193,6,0,B|359:309|142:350|89:164|89:164,1,400,0|8 +117,324,367093,2,0,B|176:384|364:408|416:309|426:294|426:294,2,300,0|0|8 +192,209,368293,2,0,B|160:113|256:41|352:113|320:209,2,300,0|0|8 +96,109,369493,6,0,B|148:5|256:-27|364:5|416:109,2,400,0|8|2 +168,224,370993,2,0,B|200:264|256:276|312:264|344:224,2,200,8|0|8 +32,184,371893,6,0,B|92:224|92:224|104:292|172:316,1,200,0|8 +256,204,372493,1,2 +340,315,372793,2,0,B|408:292|420:224|420:224|480:184,1,200,8|0 +360,120,373393,2,0,B|308:128|308:128|200:84|212:20|256:0|300:20|312:84|204:128|204:128|152:120,1,400,8|8 +40,204,374293,6,0,B|108:192|176:232|184:308,1,200,0|8 +184,308,374743,1,4 +328,308,375043,1,2 +328,308,375193,2,0,B|336:232|404:192|472:204,1,200,8|4 +340,156,375793,6,0,B|276:200|260:256|256:276|256:276|252:256|236:200|172:156,1,300,8|2 +172,84,376393,2,0,B|204:36|256:24|308:36|340:84,2,200,8|0|8 +296,152,377293,6,0,B|312:212|256:236|200:260|216:320,1,200,0|8 +216,150,377893,2,0,B|200:212|256:236|312:260|295:320,2,200,0|8|2 +256,16,378793,1,8 +140,96,379093,6,0,B|92:240|232:308|256:352|256:352|280:308|420:240|372:96,1,600,0|8 +304,120,380143,2,0,B|256:144|208:120,1,100,2|0 +68,100,380593,6,0,B|96:304|292:280|324:388,1,400,8|8 +188,385,381493,2,0,B|220:280|416:304|444:100,1,400,2|0 +304,72,382393,1,8 +184,148,382693,2,0,B|212:192|256:216|256:216|300:192|328:148,1,200,0|8 +208,72,383293,1,0 +256,204,383593,1,8 +374,272,383893,6,0,B|342:312|342:312|296:328,1,100,8|8 +216,328,384193,2,0,B|169:312|169:312|137:272,1,100,8|8 +137,195,384493,2,0,B|169:155|169:155|216:140,1,100,8|8 +296,140,384793,2,0,B|342:155|342:155|374:195,1,100,8|0 +436,232,385093,6,0,B|484:112|484:112|448:48,1,200,4|8 +380,8,385543,1,0 +348,80,385693,2,0,B|296:56|296:56|256:72|256:72|216:56|216:56|164:80,1,200,0|8 +132,8,386143,1,0 +62,50,386293,2,0,B|28:112|28:112|76:232,1,200,0|8 +120,300,386743,6,0,B|176:356|256:372|336:356|392:300,1,300,0|8 +424,228,387343,1,2 +344,240,387493,2,0,B|312:280|256:296|200:280|168:240,1,200,0|8 +88,228,387943,1,0 +140,168,388093,1,0 +256,140,388243,6,0,B|256:88,6,50,8|0|8|0|8|0|4 +256,140,389293,1,4 +296,212,389893,6,0,B|284:339|148:363|68:339,2,300,6|2|8 +256,140,390943,1,0 +216,212,391093,2,0,B|227:339|363:363|443:339,2,300,0|2|8 +368,268,392293,6,0,B|412:280|460:252|460:196|460:196|460:128,1,200,0|8 +472,56,392743,2,0,B|428:44|384:68|380:124|380:124|380:192,1,200 +300,192,393193,2,0,B|300:72|300:72|300:16|256:-4|212:16|212:72|212:72|212:192,1,400,8|8 +132,191,393943,2,0,B|132:124|132:124|128:68|84:44|40:56,1,200,2|0 +52,131,394393,2,0,B|52:196|52:196|52:252|100:280|144:268,1,200,8|0 +296,316,394993,6,0,B|440:264|424:68,1,300,8|2 +344,88,395593,2,0,B|364:244|256:288|148:244|168:88,1,400,8|8 +87,71,396343,2,0,B|72:264|216:316,1,300,2|8 +204,108,397093,6,0,B|336:108|336:108|400:140,1,200,0|8 +360,212,397543,1,2 +308,276,397693,2,0,B|176:276|176:276|112:244,1,200,0|8 +152,172,398143,1,0 +128,96,398293,6,0,B|48:32,2,100,8|0|8 +176,32,398743,2,0,B|256:92|256:92|336:32,1,200,0|8 +384,96,399193,2,0,B|464:32,2,100,8|0|4 +404,256,399793,6,0,B|320:280|320:280|348:340|304:388,1,200,8|0 +256,324,400243,1,0 +205,384,400393,2,0,B|164:340|192:280|192:280|108:256,1,200,8|0 +124,180,400843,2,0,B|204:204|204:204|240:216|260:236|264:244|256:256|248:244|252:236|272:216|308:204|308:204|388:180,1,300,2|2 +364,20,401593,6,0,B|292:24|228:68|216:132,1,200,8|0 +296,132,402043,2,0,B|284:68|220:24|148:20,1,200,2|0 +148,100,402493,1,0 +256,216,402793,1,8 +364,100,403093,6,0,B|384:116|400:144|400:204|400:204|400:292,1,200,0|8 +256,356,403693,1,0 +112,290,403993,2,0,B|112:204|112:204|112:144|128:116|148:100,1,200,8|4 +188,256,404593,6,0,B|200:200|256:164|312:200|324:256,2,200,8|0|8 +112,228,405343,1,0 +40,188,405493,2,0,B|60:160|94:147|128:148|156:160|156:160|176:136|216:120,1,200,2|8 +296,120,405943,2,0,B|336:136|356:160|356:160|384:148|417:147|452:160|472:188,1,200 +400,228,406393,1,8 +256,296,406693,6,0,B|336:344|336:344|448:328,2,200,0|8|2 +256,216,407443,1,0 +256,136,407593,2,0,B|176:88|176:88|64:104,2,200,8|0|8 +324,176,408343,5,0 +324,256,408493,1,0 +256,300,408643,1,0 +188,256,408793,1,0 +188,176,408943,1,0 +256,136,409093,1,4 +392,52,409393,6,0,B|396:144|432:192|496:204,1,200,0|8 +496,124,409843,2,0,B|433:135|397:183|393:275,1,200,2|0 +472,272,410293,2,0,B|468:328|432:360|372:360|372:360|316:360,1,200,0|8 +296,284,410743,1,0 +216,284,410893,1,0 +191,360,411043,6,0,B|140:360|140:360|80:360|44:328|40:272,1,200 +118,275,411493,2,0,B|114:183|78:135|16:124,1,200,2|8 +17,203,411943,2,0,B|80:192|116:144|120:52,1,200 +200,72,412393,6,0,B|196:140|256:164|316:140|312:72,1,200,8|0 +376,136,412843,2,0,B|348:208|256:244|164:208|136:136,1,300,2|0 +25,32,413593,5,8 +24,31,413893,2,0,B|61:43|61:43|53:76|53:76|86:88|86:88|73:121|73:121|94:141|94:141|90:178|-3:154|-27:269|49:343|155:302|155:192|155:192|217:192|217:192|217:228|217:228|225:273|267:302|267:302|295:314|308:351|287:384|253:392|221:384|200:351|213:314|241:302|241:302|283:273|291:228|291:228|291:192|291:192|353:192|353:192|353:302|459:343|537:269|513:154|418:178|414:141|414:141|435:121|435:121|422:88|422:88|455:76|455:76|447:43|447:43|484:31,1,1600,0|4 +340,104,416593,5,0 +256,104,416743,1,0 +300,32,416893,1,0 +212,32,417043,1,0 +172,104,417193,1,0 +212,172,417343,1,0 +300,172,417493,1,4 +460,216,417793,5,0 +392,328,418093,1,4 +252,380,418393,1,0 +252,380,418543,1,0 +252,380,418693,1,4 +120,328,418993,1,8 +52,216,419293,1,0 +208,144,419593,5,8 +296,144,419743,1,0 +328,59,419893,2,0,B|318:-7|198:-39|182:59|182:59,1,200,0|8 +128,132,420343,2,0,B|212:176|212:176|212:304,1,200 +300,281,420793,2,0,B|300:176|300:176|384:132,1,200,8|0 +472,156,421243,1,0 +408,220,421393,5,8 +352,384,421693,2,0,B|288:384|288:384|256:368|256:368|224:384|224:384|136:384|136:384,1,200,0|8 +104,220,422293,1,0 +156,48,422593,5,8 +180,140,422743,2,0,B|220:136|248:100|256:81|256:81|264:100|288:132|336:140,1,200 +356,48,423193,1,8 +256,204,423493,5,4 +324,368,423793,2,0,B|396:300,1,100,8|0 +188,368,424243,2,0,B|116:300,1,100,0|8 +216,153,424693,2,0,B|164:204|200:264|235:275|268:272|296:280|352:228|333:181|292:148|292:148,2,300,0|0|8 +348,44,425893,2,0,B|256:8|256:8|160:44,1,200,0|8 +53,188,426493,6,0,B|120:201|143:318|50:338|50:338,1,200,0|8 +216,360,427093,2,0,B|230:328|284:318|297:364|297:364,1,100,8|8 +344,288,427393,2,0,B|368:244|368:244|344:200,1,100,8|8 +296,132,427693,2,0,B|282:164|228:174|215:128|215:128,1,100,8|8 +168,204,427993,2,0,B|144:247|144:247|168:291,1,100,8|8 +256,248,428293,1,4 +456,340,428593,6,0,B|389:327|366:210|459:190|459:190,1,200,8|8 +336,56,429193,1,8 +256,92,429343,1,0 +176,56,429493,1,8 +256,228,429793,1,8 +334,269,429943,2,0,B|315:331|198:354|174:264|174:264,1,200 +120,332,430393,1,8 +105,147,430693,2,0,B|131:68|197:12|304:11|381:62|408:154,1,400,8|8 +392,332,431593,1,8 +184,248,431893,6,0,B|197:291|228:301|256:310|256:310|279:322|279:350|256:361|232:350|232:322|256:310|256:310|279:301|310:291|328:248,2,300,8|8|8 +328,136,433093,2,0,B|314:92|283:82|256:73|256:73|232:61|232:33|256:22|279:33|279:61|256:73|256:73|232:82|201:92|184:136,2,300,8|8|8 +64,60,434293,5,4 +256,248,434743,1,8 +448,60,435193,1,8 +309,184,435493,2,0,B|378:252|330:333|283:348|239:344|201:354|133:282|153:215|208:180|208:180,1,400,4|8 +86,328,436393,5,8 +256,248,436693,1,0 +426,328,436993,1,0 +304,160,437293,1,0 +328,72,437443,1,0 +256,24,437593,1,0 +184,72,437743,1,0 +208,160,437893,1,4 +336,304,438193,2,0,B|317:366|200:389|176:299|176:299,1,200 +49,138,438793,1,0 +149,31,439093,6,0,B|166:122|257:140|257:140|330:158|366:213|366:267|293:340|220:340|147:267|147:213|184:158|257:140|257:140|348:122|366:31,1,800 +453,145,440593,5,0 +370,358,440893,1,0 +142,357,441193,1,0 +59,145,441493,1,0 +256,20,441793,1,0 +179,207,442093,6,0,B|198:145|315:122|339:212|339:212,1,200 +256,256,442543,1,0 +256,352,442693,1,4 +64,332,452293,6,0,B|64:252|64:252|64:204|96:172|136:172,1,200,4|0 +212,84,452893,2,0,B|212:164|212:164|212:212|180:244|140:244,1,200,8|0 +300,300,453493,6,0,B|300:219|300:219|300:171|332:139|371:139,1,200 +448,50,454093,2,0,B|448:130|448:130|448:178|416:210|376:210,1,200,8|0 +200,232,454693,6,0,B|176:184|176:184|132:184|100:172|80:136|84:96,1,200 +136,36,455143,1,2 +160,108,455293,2,0,B|212:108|212:108|256:128|256:128|300:108|300:108|352:108,1,200,8|0 +376,36,455743,1,2 +428,96,455893,2,0,B|432:136|412:172|380:184|336:184|336:184|312:232,1,200 +128,272,456493,2,0,B|152:324|216:332|244:304|256:296|256:296|268:304|296:332|360:324|384:272,1,300,8|0 +428,208,457093,6,0,B|388:180|336:196|312:240|312:240|256:208|256:208|200:240|200:240|176:196|124:180|84:208,1,400,4|8 +28,152,457843,2,0,B|80:116|136:108|176:124|176:124|216:140,1,200 +295,140,458293,2,0,B|336:124|336:124|376:108|432:116|484:152,2,200,2|0|8 +156,232,459193,6,0,B|356:232,1,200 +424,188,459643,1,2 +476,252,459793,1,0 +432,320,459943,2,0,B|404:372|404:372|324:372|324:372|296:320,1,200,0|2 +216,318,460393,2,0,B|188:372|188:372|108:372|108:372|80:320,1,200,0|2 +36,252,460843,1,0 +88,188,460993,1,0 +216,92,461293,6,0,B|176:52,2,50 +296,92,461593,2,0,B|336:52,2,50 +296,172,461893,2,0,B|384:172|384:172|424:172|456:192|472:232,1,200,4|8 +464,312,462343,2,0,B|424:304|400:280|388:252|388:252|320:252|320:252|296:272,1,200,0|2 +216,272,462793,2,0,B|192:252|192:252|124:252|124:252|112:280|88:304|48:312,1,200,8|0 +40,230,463243,2,0,B|56:192|88:172|128:172|128:172|216:172,1,200,0|2 +216,92,463693,2,0,B|216:56|216:56|216:28|240:4|272:4|296:28|296:56|296:56|296:92,1,200,0|8 +424,192,464293,6,0,B|464:264|464:264|384:352,1,200,0|8 +127,351,464893,2,0,B|48:264|48:264|88:192,1,200,0|8 +140,252,465343,1,0 +164,176,465493,2,0,B|204:188|236:244|204:300|204:300|308:300|308:300|276:244|308:188|348:176,1,400,2|0 +324,100,466243,1,0 +256,144,466393,1,8 +188,104,466543,1,0 +256,56,466693,1,0 +408,108,466993,6,0,B|400:152|364:180|320:184|320:184|284:228|256:216|228:228|192:184|192:184|148:180|112:152|104:108,1,400,8|8 +24,244,467893,2,0,B|148:256|148:256|176:284|212:292,1,200,0|8 +299,292,468343,2,0,B|336:284|364:256|364:256|488:244,1,200,2|0 +448,324,468793,6,0,B|404:328|404:328|336:372|256:388|176:372|108:328|108:328|64:324,1,400,8|8 +96,252,469543,2,0,B|200:208|216:104,1,200,0|2 +295,103,469993,2,0,B|312:208|416:252,1,200,8|0 +360,312,470443,5,0 +296,268,470593,1,0 +256,204,470743,1,0 +216,268,470893,1,0 +152,312,471043,1,0 +216,360,471193,1,0 +296,360,471343,1,0 +380,384,471493,6,0,B|436:368|456:304|432:252|376:236,1,200,4|0 +396,160,471943,1,2 +320,184,472093,2,0,B|300:136|300:136|256:116|256:116|212:136|212:136|192:184,1,200,0|8 +116,160,472543,1,2 +134,236,472693,2,0,B|80:252|56:304|76:368|132:384,1,200,0|8 +156,312,473143,2,0,B|356:312,1,200 +336,236,473593,1,8 +256,236,473743,1,0 +176,236,473893,6,0,B|96:240|44:192|32:124,1,200,2|8 +72,56,474343,2,0,B|152:52|204:100|216:168,1,200 +296,166,474793,2,0,B|308:100|360:52|440:56,1,200,8|2 +479,125,475243,2,0,B|468:192|416:240|336:236,1,200 +256,236,475693,1,0 +212,304,475843,1,0 +256,372,475993,1,0 +300,304,476143,1,0 +256,236,476293,6,0,B|256:32,1,200,4|0 +336,36,476743,1,2 +416,36,476893,2,0,B|416:76|384:112|340:116|340:116|340:160|340:200,1,200,0|8 +172,195,477493,2,0,B|172:160|172:116|172:116|128:112|96:76|96:36,1,200,0|8 +176,36,477943,2,0,B|224:44|244:88|244:88|268:88|268:88|288:44|336:36,1,200 +352,116,478393,6,0,B|332:152|316:156|296:164|296:164|296:220|296:220|296:288,2,200,8|2|8 +160,116,479293,2,0,B|180:152|196:156|216:164|216:164|216:220|216:220|216:288,2,200,0|8|0 +120,184,480043,5,0 +140,260,480193,1,0 +216,288,480343,1,0 +256,356,480493,1,0 +296,288,480643,1,0 +372,260,480793,1,0 +392,184,480943,1,0 +377,106,481093,6,0,B|449:94|503:168|451:239,1,200,4|0 +392,184,481543,2,0,B|341:252|395:327|467:315,1,200,2|0 +412,388,481993,2,0,B|312:372|304:316|320:268|280:280|256:260|232:280|192:268|208:316|200:372|100:388,1,400,8|8 +43,315,482743,2,0,B|116:327|170:252|120:184,1,200,2|0 +59,238,483193,2,0,B|8:168|62:94|135:106,1,200,8|0 +296,264,483793,6,0,B|372:264|372:264|400:240|400:240|432:256|432:256|456:244|456:244|480:244,2,200,8|0|8 +256,196,484543,1,2 +216,120,484693,2,0,B|140:120|140:120|112:144|112:144|80:128|80:128|56:140|56:140|32:140,2,200,0|8|0 +351,32,485593,6,0,B|276:104|360:264|480:68|540:348|412:348|352:348|352:348|295:348,1,500,8|0 +216,348,486493,2,0,B|160:348|160:348|100:348|-28:348|32:68|152:264|236:104|160:32,1,500,2|0 +104,88,487393,5,8 +256,204,487693,1,2 +408,88,487993,1,8 +188,164,488293,5,8 +188,84,488443,1,8 +256,40,488593,1,8 +324,84,488743,1,8 +324,164,488893,1,8 +256,204,489043,1,8 +188,248,489193,1,8 +324,248,489343,1,8 +256,292,489493,6,0,B|256:340,8,50,8|0|8|0|8|0|8|0|8 +324,248,490243,1,0 +296,172,490393,1,8 +216,172,490543,1,0 +188,248,490693,1,4 +176,80,490993,6,0,B|195:18|312:-5|336:85|336:85,1,200,8|8 +396,252,491593,2,0,B|392:192|442:160|442:160,1,100,8|0 +488,240,491893,1,8 +340,340,492193,1,8 +295,265,492343,2,0,B|317:234|297:196|271:187|253:182|228:183|180:218|200:250|227:280|227:280,1,200 +172,340,492793,1,8 +24,240,493093,1,8 +73,165,493243,2,0,B|118:192|116:252,1,100,0|8 +169,78,493693,6,0,B|223:78|223:78|255:46|255:46|287:78|287:78|343:78,1,200,8|8 +375,277,494293,6,0,B|346:370|169:405|133:269|133:269,2,300,8|8|8 +315,126,495493,2,0,B|347:77|317:21|282:6|248:-1|209:2|143:56|170:103|211:148|211:148,2,300,8|8|8 +184,248,496693,2,0,B|197:291|228:301|256:310|256:310|279:322|279:350|256:361|232:350|232:322|256:310|256:310|279:301|310:291|328:248,2,300,8|8|8 +101,109,497893,6,0,B|104:69|146:40|188:52|188:52|217:4|256:6|297:6|321:51|321:51|363:38|412:70|414:121,1,400,8|8 +339,285,498793,2,0,B|332:301|311:315|290:309|290:309|275:333|255:336|232:331|221:310|221:310|202:316|175:303|169:277,2,200,8|8|8 +256,86,499693,5,0 +184,138,499843,1,0 +212,222,499993,1,0 +300,222,500143,1,0 +328,138,500293,1,4 +424,276,500593,5,0 +256,372,500893,1,0 +88,276,501193,1,0 +88,276,501343,1,0 +88,276,501493,1,0 +160,92,501793,1,0 +160,92,501943,1,0 +352,92,502243,1,0 +352,92,502393,1,0 +256,248,502693,5,0 +301,170,502843,2,0,B|283:152|255:151|229:152|210:170,1,100 +338,292,503293,2,0,B|316:352|200:377|175:288|175:288,1,200 +91,154,503893,6,0,B|44:150|21:102|10:42|65:7|131:-10|181:42|166:103|166:103,2,300 +421,154,505093,2,0,B|468:150|491:102|502:42|447:7|381:-10|331:42|346:103|346:103,2,300 +213,259,506293,2,0,B|161:310|197:370|232:381|265:378|293:386|349:334|330:287|289:254|289:254,2,300 +170,74,507493,6,0,B|224:74|224:74|256:42|256:42|288:74|288:74|344:74,1,200 +448,224,508093,1,0 +330,360,508393,2,0,B|315:315|255:300|255:300|195:315|182:360,1,200 +64,224,508993,1,0 +256,152,509293,5,0 +328,100,509443,1,0 +300,16,509593,1,0 +212,16,509743,1,0 +184,100,509893,1,4 +256,264,510193,1,0 +376,72,510493,1,0 +136,76,510793,1,0 +380,73,511093,6,0,B|445:147|468:248|406:346|328:392|268:399|149:402|86:328|25:223|84:96|152:64,1,800 +256,196,512593,1,0 +256,196,512893,5,0 +180,240,513043,1,0 +180,328,513193,1,0 +256,372,513343,1,0 +332,328,513493,1,0 +332,240,513643,1,0 +336,152,513793,6,0,B|368:138|378:84|332:71|332:71,1,100,0|0 +256,32,514093,1,0 +176,72,514243,2,0,B|144:86|134:140|180:153|180:153,1,100 +256,196,514543,1,0 +182,251,514693,2,0,B|256:319|256:319|326:249,1,200,4|0 +335,255,515068,1,0 +344,263,515143,1,0 +352,271,515218,1,0 +360,279,515293,2,0,B|415:295|465:274|495:228|488:171,1,200,8|0 +398,188,515743,1,0 +386,180,515818,1,0 +374,172,515893,2,0,B|318:155|268:176|238:222|245:279,2,200,8|0|8 +356,28,516793,1,8 +176,24,517093,5,0 +80,176,517393,1,8 +88,180,517468,1,0 +96,184,517543,1,0 +104,188,517618,1,0 +112,192,517693,2,0,B|168:175|218:196|248:242|241:299,1,200,8|0 +144,288,518143,1,0 +136,292,518218,1,0 +128,296,518293,2,0,B|73:312|23:291|-7:245|0:188,1,200,8|8 +48,100,518743,1,0 +48,100,518818,1,0 +48,100,518893,2,0,B|124:100|124:100|168:100|208:72|216:32,1,200,0|8 +464,284,519493,2,0,B|388:284|388:284|344:284|304:312|296:352,1,200,4|8 +216,352,519943,6,0,B|168:352,2,50,0|0|8 +296,32,520393,2,0,B|344:32,2,50,8|0|0 +204,68,520693,2,0,B|248:120|248:120|380:120,1,200,0|8 +308,316,521293,2,0,B|264:264|264:264|132:264,1,200,0|8 +24,96,521893,6,0,B|56:136|56:136|156:124|156:124|188:160,1,200,0|8 +256,232,522343,2,0,B|256:280,2,50,0|0|8 +324,159,522643,1,0 +324,159,522793,2,0,B|356:124|356:124|456:136|456:136|488:96,1,200,8|8 +428,16,523243,5,4 +24,288,523693,5,8 +84,368,523843,1,4 +256,120,524293,5,4 +452,156,524593,2,0,B|424:100,2,50,8|0|0 +504,240,524893,2,0,B|456:280|360:252|348:176,1,200,8|0 +163,176,525493,2,0,B|152:252|56:280|8:240,1,200,0|8 +60,156,525943,1,0 +60,156,526093,2,0,B|88:100,2,50,8|0|0 +60,156,526393,1,0 +300,336,526693,6,0,B|340:280|340:280|384:292|440:284|460:248,1,200,0|8 +388,180,527143,2,0,B|404:128,2,50,0|0|8 +124,180,527593,2,0,B|108:128,2,50,8|0|0 +52,249,527893,2,0,B|72:284|128:292|172:280|172:280|212:336,1,200,0|8 +256,140,528493,1,0 +256,140,528793,2,0,B|256:40,2,100,8|0|4 +460,192,529393,6,0,B|528:192,2,50,8|0|0 +360,192,529693,1,8 +52,188,529993,2,0,B|-16:188,2,50,8|0|0 +152,192,530293,1,8 +312,308,530593,1,8 +256,108,530893,1,0 +200,308,531193,1,8 +172,112,531493,6,0,B|172:60|212:16|276:12|316:52,1,200,0|8 +340,272,532093,2,0,B|340:324|300:368|236:372|196:332,1,200,0|8 +56,192,532543,5,0 +56,192,532618,1,0 +56,192,532693,1,0 +8,100,532843,1,4 +460,192,533293,5,8 +504,284,533443,1,4 +256,192,550693,12,4,553093 +48,116,553393,6,0,B|114:126|146:246|48:262|48:262,1,200,0|0 +183,36,553993,2,0,B|193:102|313:134|329:36|329:36,1,200,0|0 +464,262,554593,2,0,B|398:252|366:132|464:116|464:116,1,200,0|0 +300,352,555193,1,0 +328,268,555343,1,0 +256,212,555493,1,0 +184,268,555643,1,0 +212,352,555793,1,0 +108,200,556093,5,0 +168,40,556393,1,0 +344,40,556693,1,0 +404,200,556993,1,0 +256,288,557293,1,0 +256,108,557593,5,0 +212,186,557743,1,0 +301,185,557893,1,4 +174,312,558193,2,0,B|183:356|231:356|255:340|255:340|279:356|327:356|335:308,1,200 +470,164,558793,2,0,B|425:158|418:126|411:107|417:88|417:88|398:95|379:89|351:80|344:41,1,200 +166,43,559393,2,0,B|160:80|132:89|113:95|94:88|94:88|100:107|93:126|86:158|42:164,1,200 +152,288,559993,2,0,B|192:384,1,100 +256,320,560293,1,4 +321,380,560443,2,0,B|360:288,1,100 +295,119,560893,2,0,B|317:88|297:50|271:41|253:36|228:37|180:72|200:104|227:134|227:134,1,200 +56,200,561493,5,0 +128,272,561643,1,0 +164,298,561718,1,0 +181,339,561793,1,0 +256,264,561943,1,0 +331,339,562093,1,0 +348,298,562168,1,0 +384,272,562243,1,0 +432,176,562393,1,0 +360,88,562543,1,0 +256,56,562693,1,4 +152,88,562843,1,0 +80,176,562993,1,0 +213,343,563293,6,0,B|199:287|199:287|254:259|254:259|310:287|310:287|296:343,1,240.000005722046 +437,158,563893,2,0,B|471:123|471:123|465:38|381:32|381:32|341:72,1,240.000005722046 +166,67,564493,2,0,B|131:32|131:32|47:38|41:123|41:123|76:158,1,240.000005722046 +200,248,564943,2,0,B|176:304|176:304|200:360,1,120.000002861023,0|4 +312,358,565243,2,0,B|336:304|336:304|312:248,1,120.000002861023 +256,4,565693,5,0 +456,96,565993,1,0 +456,296,566293,1,0 +256,376,566593,1,0 +56,296,566893,1,0 +56,96,567193,1,0 +191,119,567343,2,0,B|231:167|191:231,1,120.000002861023 +320,229,567643,2,0,B|280:165|320:117,1,120.000002861023 +354,331,568093,6,0,B|303:321|271:347|254:380|254:380|238:347|205:321|155:331,1,240.000005722046 +157,90,568693,2,0,B|207:99|239:73|256:40|256:40|272:73|305:99|355:90,1,240.000005722046 +477,296,569293,1,0 +256,204,569593,1,0 +212,348,569743,1,0 +332,264,569893,1,0 +180,264,570043,1,0 +300,348,570193,1,0 +32,296,570493,1,0 +160,96,570793,6,0,B|182:21|324:-6|353:102|353:102,2,240.000005722046 +112,336,571693,2,0,B|161:288|160:224,1,120.000002861023 +256,280,571993,1,0 +352,229,572143,2,0,B|351:288|400:336,1,120.000002861023,0|4 +356,64,572593,6,0,B|306:48|289:14|289:14|256:31|222:14|222:14|205:48|155:64|155:64,1,240.000005722046,0|0 +376,192,573193,2,0,B|136:192,1,240.000005722046 +356,320,573793,2,0,B|306:336|289:370|289:370|256:353|222:370|222:370|205:336|155:320|155:320,1,240.000005722046 +153,67,574393,6,0,B|200:83|208:137|192:165|172:188|144:206|71:203|65:157|70:105|70:105,1,259.999995350838,0|4 +359,67,574993,2,0,B|311:83|303:137|319:165|339:188|367:206|440:203|446:157|441:105|441:105,1,259.999995350838 +367,348,575593,2,0,B|297:348|297:348|256:306|256:306|215:348|215:348|143:348,1,259.999995350838 +64,128,576193,1,0 +364,64,576493,2,0,B|328:10|256:-25|183:10|147:64,1,259.999995350838 +196,179,576943,2,0,B|224:208|286:211|320:176,1,129.999997675419,0|4 +138,298,577393,2,0,B|171:339|257:370|336:339|373:299,1,259.999995350838 +440,144,577993,5,0 +256,32,578293,1,0 +72,144,578593,1,0 +180,308,578893,1,0 +256,360,579043,1,0 +336,296,579193,1,0 +304,216,579343,1,0 +208,216,579493,1,4 +128,152,579643,5,0 +92,56,579793,2,0,B|172:44|212:104|212:104,1,129.999997675419 +303,99,580093,2,0,B|347:46|420:56,1,129.999997675419 +384,152,580393,1,0 +304,216,580543,1,0 +410,291,580693,5,4 +400,311,580768,1,0 +383,327,580843,1,0 +364,339,580918,1,0 +343,345,580993,1,0 +322,347,581068,1,0 +299,342,581143,1,0 +278,333,581218,1,0 +260,320,581293,1,4 +234,333,581368,1,0 +213,342,581443,1,0 +190,347,581518,1,0 +169,345,581593,1,0 +148,339,581668,1,0 +129,327,581743,1,0 +112,311,581818,1,0 +102,291,581893,1,4 +188,252,582043,5,0 +324,252,582193,1,0 +256,188,582343,1,0 +352,152,582493,1,0 +160,152,582643,1,0 +256,92,582793,1,0 +256,92,582943,1,0 +160,52,583093,6,0,B|96:52|32:108|44:192|104:240,1,259.999995350838,0|0 +256,312,583543,1,0 +408,240,583693,2,0,B|468:192|480:108|416:52|352:52,1,259.999995350838 +352,152,584143,1,0 +340,296,584293,2,0,B|316:228|256:200|256:200|196:228|172:296,1,259.999995350838 +256,312,584743,1,0 +172,294,584893,2,0,B|52:244,1,129.999997675419 +36,152,585193,2,0,B|156:204,1,129.999997675419 +236,232,585493,6,0,B|252:180|216:136|216:136|72:72,1,259.999995350838 +292,108,585943,2,0,B|408:44,1,129.999997675419 +320,184,586243,2,0,B|452:184,1,129.999997675419 +312,268,586543,1,0 +312,268,586693,6,0,B|432:316,1,129.999997675419,4|0 +332,376,586993,2,0,B|296:328|292:256|360:184|444:200,1,259.999995350838 +180,376,587593,2,0,B|216:328|220:256|152:184|68:200,1,259.999995350838 +96,124,588043,2,0,B|172:128|216:172,1,129.999997675419 +416,124,588343,2,0,B|340:128|296:172,1,129.999997675419 +140,52,588793,2,0,B|200:64|256:108|256:108|312:64|372:52,1,259.999995350838 +256,12,589243,1,0 +256,12,589393,1,0 +372,224,589693,5,0 +140,224,589993,1,0 +292,360,590293,2,0,B|337:315|305:263|275:254|246:256|222:249|174:294|190:335|226:364|226:364,2,259.999995350838 +364,141,591193,1,0 +256,101,591343,1,0 +148,141,591493,1,4 +363,54,591793,6,0,B|327:0|255:-35|182:0|146:54,2,259.999995350838 +256,103,592543,1,0 +362,150,592693,2,0,B|326:204|254:239|181:204|145:150,1,259.999995350838 +138,299,593143,1,0 +380,297,593443,1,0 +379,296,593593,1,0 +368,184,593893,6,0,B|308:180|244:244|252:324|304:376,1,259.999995350838 +380,288,594343,1,0 +272,320,594493,2,0,B|224:272|152:288,1,129.999997675419 +64,216,594793,2,0,B|20:272|52:340,1,129.999997675419 +144,200,595093,6,0,B|204:204|268:140|260:60|208:8,1,259.999995350838 +132,92,595543,1,0 +240,64,595693,2,0,B|288:112|360:96,1,129.999997675419 +448,168,595993,2,0,B|492:112|460:44,2,129.999997675419,0|0|4 +136,320,596593,1,0 +136,320,596743,1,0 +376,320,597043,1,0 +376,320,597193,1,0 +376,320,597493,6,0,B|424:268|422:191|375:126|311:112,1,259.999995350838 +136,320,597943,2,0,B|87:268|89:191|136:126|200:112,1,259.999995350838 +256,48,598393,1,0 +196,348,598693,2,0,B|256:392|316:348,1,129.999997675419 +256,272,598993,1,0 +256,272,599143,1,0 +308,144,599293,6,0,B|304:76|344:16|432:0|492:60,1,259.999995350838 +412,132,599743,1,0 +336,276,599893,2,0,B|340:204|256:148|172:204|176:276,1,259.999995350838 +100,132,600343,1,0 +25,55,600493,2,0,B|79:0|167:15|207:75|203:143,1,259.999995350838 +256,272,600943,1,0 +256,192,601018,12,0,602293 +256,192,602368,12,0,603493 +180,344,603793,6,0,B|205:286|205:286|257:262|257:262|310:286|310:286|334:344,1,240.000005722046 +488,176,604393,2,0,B|434:168|426:130|417:108|424:85|424:85|402:93|379:86|346:75|337:29,1,240.000005722046 +172,32,604993,2,0,B|164:76|131:86|108:93|86:85|86:85|93:108|85:130|76:168|24:176,1,240.000005722046 +168,320,605593,1,0 +256,360,605743,1,0 +344,320,605893,1,4 +453,145,606193,5,0 +256,20,606493,1,0 +59,145,606793,1,0 +142,357,607093,1,0 +370,358,607393,1,0 +256,188,607693,1,0 +315,98,607843,2,0,B|259:68|186:76|145:139|146:199,1,240.000005722046 +197,285,608293,2,0,B|250:314|325:307|366:246|366:183,1,240.000005722046 +464,288,608893,5,0 +256,156,609193,1,0 +48,288,609493,1,0 +166,112,609793,2,0,B|184:57|256:39|256:39|328:57|344:112,1,240.000005722046 +320,192,610243,2,0,B|320:256|255:272|255:272|182:292|198:370,1,240.000005722046 +314,370,610693,2,0,B|330:292|256:272|256:272|192:256|192:192,1,240.000005722046,4|0 +384,80,611293,5,0 +128,80,611593,1,0 +128,304,611893,1,0 +384,304,612193,1,0 +256,128,612493,1,0 +165,185,612643,2,0,B|217:196|249:228|257:252|257:252|265:228|289:196|352:183,1,240.000005722046 +346,280,613093,6,0,B|327:359|188:394|160:272|160:272,1,240.000005722046 +64,88,613693,1,0 +228,32,613993,2,0,B|256:24|256:24|284:32,4,60.0000014305115 +164,136,614443,2,0,B|200:136|200:136|224:112|224:112|256:152|256:152|288:112|288:112|312:136|312:136|352:136,1,240.000005722046 +420,208,614893,1,0 +368,300,615043,1,0 +256,344,615193,1,0 +144,300,615343,1,0 +92,208,615493,1,4 +420,48,615793,5,0 +92,48,616093,1,0 +92,336,616393,1,0 +420,336,616693,1,0 +256,40,616993,1,0 +88,200,617293,1,0 +160,272,617443,1,0 +256,304,617593,1,0 +352,272,617743,1,0 +424,200,617893,1,0 +164,76,618193,6,0,B|183:155|322:190|350:68|350:68,1,240.000005722046 +345,308,618793,2,0,B|323:230|188:196|161:312|161:312,1,240.000005722046 +388,192,619393,1,0 +124,192,619693,1,0 +200,108,619843,2,0,B|256:128|256:128|312:108,1,120.000002861023 +256,16,620143,1,0 +396,48,620293,6,0,B|451:66|469:138|469:138|451:210|396:226,1,240.000005722046 +116,336,620893,2,0,B|61:318|43:246|43:246|61:174|116:158,1,240.000005722046 +388,280,621493,5,0 +256,48,621793,1,0 +124,280,622093,1,0 +56,196,622243,2,0,B|116:172|116:172|136:116|136:116,1,120.000002861023 +202,192,622543,2,0,B|256:218|256:218|309:192,1,120.000002861023 +377,119,622843,2,0,B|376:116|396:172|396:172|456:196,1,120.000002861023 +400,288,623143,1,0 +304,344,623293,1,0 +256,360,623368,1,0 +208,344,623443,1,0 +128,272,623593,5,0 +160,168,623743,1,0 +256,128,623893,1,0 +352,168,624043,1,0 +384,272,624193,1,0 +256,248,624343,1,0 +352,168,624493,5,0 +368,112,624568,1,0 +352,64,624643,1,0 +312,24,624718,1,0 +256,8,624793,1,0 +200,24,624868,1,0 +160,64,624943,1,0 +144,112,625018,1,0 +160,168,625093,1,4 +84,244,625243,5,0 +152,336,625393,1,0 +256,364,625543,1,0 +360,336,625693,1,0 +428,244,625843,1,0 +428,140,625993,1,0 +360,48,626143,1,0 +256,20,626293,1,0 +152,48,626443,1,0 +84,140,626593,1,0 +206,158,626743,6,0,B|222:129|256:125|289:129|305:158,1,120.000002861023 +376,240,627043,1,0 +304,320,627193,2,0,B|287:349|254:353|220:349|204:320,1,120.000002861023 +136,240,627493,1,0 +256,248,627643,1,0 +312,144,627793,5,0 +312,144,627868,1,0 +312,144,627943,1,0 +344,48,628093,1,0 +256,0,628243,1,0 +168,48,628393,1,0 +200,144,628543,1,0 +96,168,628693,5,0 +88,272,628843,1,0 +192,320,628993,1,0 +256,232,629143,1,0 +312,144,629293,1,0 +416,168,629443,1,0 +424,272,629593,1,0 +320,320,629743,1,0 +256,232,629893,1,0 +200,144,630043,5,0 +208,40,630193,1,0 +256,16,630268,1,0 +304,40,630343,1,0 +312,144,630493,1,0 +416,200,630643,2,0,B|488:304,1,120.000002861023 +408,376,630943,2,0,B|339:277,1,120.000002861023 +256,344,631243,1,0 +172,277,631393,2,0,B|104:376,1,120.000002861023 +27,298,631693,2,0,B|96:200,1,120.000002861023 +93,91,631993,5,0 +206,63,632143,2,0,B|222:33|255:29|289:33|305:63,1,120.000002861023 +419,91,632443,1,0 +352,176,632593,1,0 +360,184,632668,1,0 +368,192,632743,1,0 +400,296,632893,2,0,B|456:278|456:278|476:222,1,120.000002861023 +308,356,633193,2,0,B|289:356|289:356|265:346|265:346|256:360|256:360|246:346|246:346|227:356|227:356|208:356,1,120.000002861023 +35,222,633493,2,0,B|55:278|55:278|112:296,1,120.000002861023 +184,96,633793,5,0 +256,272,633943,1,0 +328,96,634093,1,0 +160,200,634243,1,0 +352,200,634393,1,0 +256,32,634543,1,0 +256,168,634693,5,4 +352,328,634993,5,0 +400,88,635293,1,0 +160,40,635593,1,0 +112,248,635893,1,0 +276,357,636193,5,0 +431,167,636493,1,0 +242,12,636793,1,0 +102,173,637093,1,0 +205,350,637393,5,0 +425,244,637693,1,0 +319,23,637993,1,0 +124,110,638293,1,0 +138,310,638593,5,0 +383,315,638893,1,0 +389,71,639193,1,0 +175,59,639493,1,0 +99,249,639793,5,0 +319,358,640093,1,0 +427,138,640393,1,0 +239,38,640693,1,0 +89,186,640993,5,0 +252,369,641293,1,0 +435,206,641593,1,0 +297,43,641893,1,0 +107,116,642193,5,0 +177,351,642493,1,0 +412,281,642793,1,0 +304,240,642943,5,0 +256,224,643018,1,0 +208,240,643093,1,0 +136,155,643243,2,0,B|187:189|256:155|307:103|307:51|290:17|256:0|221:17|204:51|204:103|256:155|324:189|376:155,1,480.000011444092 +40,44,671736,6,0,B|40:164,1,100,8|8 +120,184,672236,2,0,B|120:76,1,100,8|0 +200,126,672736,2,0,B|217:74|276:53|341:72|356:135,1,200,4|8 +436,72,673486,1,0 +484,148,673736,1,8 +412,204,673986,2,0,B|408:244|424:280|464:296|464:296,1,100,0|8 +404,368,674486,5,0 +325,319,674736,2,0,B|299:294|264:292|232:320|232:320|204:348|157:346|140:313,1,200,8|8 +64,352,675486,2,0,B|16:316|12:208|68:160,1,200 +122,106,676236,2,0,B|174:90|190:38,1,100,8|0 +267,84,676736,2,0,B|243:131|281:187|370:171|373:107|349:70,1,200,8|8 +428,28,677486,5,0 +488,96,677736,1,8 +424,160,677986,1,0 +484,225,678236,2,0,B|470:274|470:274|486:322,1,100,8|0 +412,368,678736,2,0,B|308:368,1,100,8|0 +252,300,679236,1,8 +184,360,679486,5,0 +108,312,679736,2,0,B|48:328|4:284|4:204|68:188|68:188,1,200,8|8 +148,148,680486,2,0,B|194:117|188:60,1,100,0|8 +268,20,680986,1,0 +332,80,681236,2,0,B|448:48,1,100,8|0 +484,124,681736,2,0,B|388:152,1,100,8|0 +376,244,682236,6,0,B|492:212,1,100,8|0 +496,304,682736,1,8 +328,373,683236,2,0,B|313:319|252:296|191:320|176:383,1,200,8|8 +88,328,683986,2,0,B|16:248|48:136|48:136,1,200 +120,192,684736,2,0,B|124:75|216:16,1,200,8|8 +280,80,685486,5,0 +360,32,685736,2,0,B|480:32|480:32,1,100,8|0 +488,120,686236,2,0,B|440:128|440:168|488:176,1,100,8|0 +432,248,686736,2,0,B|480:256|480:296|432:304,1,100,8|0 +360,360,687236,1,8 +288,304,687486,5,0 +216,360,687736,2,0,B|104:360|104:360,1,100,8|0 +72,280,688236,2,0,B|184:280|184:280,1,100,8|0 +136,200,688736,2,0,B|85:203|44:162|36:100|88:60,1,200,12|8 +169,24,689486,1,0 +256,56,689736,2,0,B|360:36,1,100,8|0 +424,88,690236,6,0,B|444:188,1,100,8|0 +368,232,690736,1,8 +216,344,691236,2,0,B|223:318|255:306|287:319|295:351,1,100,8|0 +144,232,692236,1,8 +68,186,692486,2,0,B|88:88,1,100,0|8 +256,200,693236,5,8 +336,72,693736,1,8 +256,24,693986,1,0 +176,72,694236,1,8 +177,236,694736,2,0,B|198:284|254:310|321:285|336:232,1,200,8|8 +408,280,695486,1,0 +408,280,695736,5,8 +256,368,696236,1,8 +104,280,696736,1,4 +104,104,697236,1,8 +256,16,697736,1,8 +408,104,698236,5,8 +408,104,698486,1,0 +408,104,698736,1,8 +338,275,699236,2,0,B|315:320|259:346|192:321|176:269,1,200,8|8 +175,175,699986,2,0,B|198:130|254:104|321:129|337:181,1,200 +257,228,700736,5,8 +256,48,701236,1,8 +176,320,701736,1,8 +256,360,701986,1,0 +336,320,702236,1,8 +352,144,702736,2,0,B|287:143|287:143|255:127|255:127|223:143|223:143|151:143,1,200,8|8 +300,32,703736,5,8 +212,32,703986,1,0 +60,121,704486,6,0,B|94:207|94:207|50:304,1,200,8|0 +49,304,705236,1,8 +124,344,705486,2,0,B|169:247|169:247|257:217,1,200,8|0 +452,120,706236,1,8 +452,120,706486,2,0,B|417:207|417:207|462:304,1,200,8|0 +463,304,707236,1,8 +387,343,707486,2,0,B|342:247|342:247|254:217,1,200,8|0 +158,53,708236,1,8 +158,53,708486,2,0,B|223:52|223:52|255:36|255:36|287:52|287:52|359:52,1,200,8|0 +352,138,709236,1,8 +352,138,709486,2,0,B|287:139|287:139|255:155|255:155|223:139|223:139|151:139,1,200,8|0 +256,285,710236,5,8 +100,340,710486,1,8 +204,340,710736,5,0 +180,304,710861,1,0 +180,260,710986,1,0 +212,220,711111,1,0 +256,208,711236,1,0 +300,220,711361,1,0 +332,260,711486,1,0 +332,304,711611,1,0 +308,340,711736,1,0 +412,340,711986,37,0 +452,164,712486,21,0 +370,97,712736,2,0,B|340:54|262:14|173:45|137:104,1,259.999995350838,4|8 +370,287,713736,2,0,B|340:330|262:370|173:339|137:280,1,259.999995350838,0|8 +194,214,714486,2,0,B|258:263|325:207,1,129.999997675419,0|8 +256,144,714986,1,8 +96,288,715486,6,0,B|32:240|16:192|16:192|32:128|112:80,1,259.999995350838,8|8 +256,192,716236,1,8 +416,288,716486,2,0,B|480:240|496:192|496:192|480:128|400:80,1,259.999995350838,8|0 +200,76,717236,6,0,B|214:55|253:35|298:50|316:80,1,129.999997675419,8|0 +256,336,717736,1,8 +160,180,717986,2,0,B|174:243|252:283|341:252|352:168,2,259.999995350838,8|8|8 +320,344,719236,6,0,B|184:344,1,129.999997675419,8|0 +440,168,719736,2,0,B|360:36,1,129.999997675419,8|0 +72,168,720236,2,0,B|152:36,1,129.999997675419,8|0 +256,216,720736,2,0,B|256:296|256:296|360:292|368:200|340:136|304:100|196:92|156:152|149:244|204:280,1,519.999990701676,4|0 +348,360,722236,5,8 +256,384,722486,1,0 +172,360,722736,1,8 +256,208,723236,1,8 +299,22,723736,1,8 +214,25,723986,2,0,B|173:80|209:160|333:145|330:56|296:19,1,259.999995350838,8|8 +404,172,724986,5,8 +344,240,725236,1,8 +256,260,725486,1,0 +168,236,725736,1,8 +108,172,725986,1,0 +140,340,726486,2,0,B|170:383|248:423|337:392|373:333,1,259.999995350838,8|0 +316,268,727236,2,0,B|252:317|185:261,1,129.999997675419,8|0 +256,176,727736,5,8 +160,128,727986,1,0 +192,96,728111,1,0 +212,48,728236,1,0 +256,20,728361,1,0 +300,48,728486,1,0 +320,96,728611,1,0 +352,128,728736,1,4 +400,224,728986,6,0,B|400:288|400:288|464:352,1,129.999997675419 +352,368,729486,1,0 +279,292,729736,2,0,B|297:263|279:224|217:231|219:275|235:294,1,129.999997675419 +160,368,730236,1,0 +61,333,730486,2,0,B|107:286|107:286|107:222,1,129.999997675419 +195,167,730986,2,0,B|256:143|256:143|316:167,1,129.999997675419 +379,84,731486,2,0,B|256:35|256:35|135:84,1,259.999995350838 +207,173,732236,5,0 +305,174,732486,2,0,B|373:206|373:323|285:353|256:323|256:323|212:279|226:235|285:235|299:279|256:323|256:323|212:353|138:309|138:206|214:170,1,649.999988377094 +162,79,733986,1,0 +350,79,734486,1,0 +365,286,734986,2,0,B|335:243|257:203|168:234|132:293,1,259.999995350838 +195,370,735736,6,0,B|259:419|326:363,2,129.999997675419 +137,282,736486,1,0 +79,182,736736,2,0,B|202:174|221:20|153:-13|84:20|84:89|187:123|256:20|256:20|309:109|412:102|451:11|334:-40|296:20|329:166|352:180|459:186,1,779.999986052513,4|0 +344,248,738486,5,0 +256,192,738736,1,0 +168,248,738986,1,0 +355,355,739486,2,0,B|280:360|256:287|256:287|232:360|155:355,1,259.999995350838 +80,288,740236,1,0 +58,186,740486,6,0,B|28:128|76:66|76:66|158:49|196:100,1,259.999995350838 +256,184,741236,2,0,B|256:216|256:216|240:232|240:232|272:240|272:240|256:256|256:256|256:280,2,129.999997675419 +316,99,741986,2,0,B|354:49|436:66|436:66|484:128|454:186,1,259.999995350838 +315,349,742986,6,0,B|251:398|184:342,1,129.999997675419 +205,246,743486,1,0 +307,246,743736,1,0 +368,160,743986,5,0 +324,156,744111,1,0 +288,132,744236,1,0 +256,100,744361,1,0 +224,132,744486,1,0 +188,156,744611,1,0 +144,160,744736,1,4 +256,12,751736,5,8 +181,62,751986,1,0 +209,150,752236,1,8 +301,150,752486,1,0 +329,62,752736,1,4 +328,243,753236,2,0,B|313:297|252:320|191:296|176:233,1,200,8|8 +344,356,754236,5,8 +256,384,754486,1,0 +168,356,754736,1,8 +126,200,755236,1,8 +183,128,755486,2,0,B|233:132|253:72|253:72|273:132|336:128,1,200 +385,200,756236,1,8 +296,240,756486,2,0,B|288:216|259:203|225:213|217:244,1,100,0|8 +256,372,757236,5,8 +416,228,757736,1,8 +356,56,758236,1,8 +156,56,758736,1,8 +84,228,759236,1,8 +141,298,759486,5,0 +200,225,759736,1,4 +183,187,759861,1,0 +188,145,759986,1,0 +213,111,760111,1,0 +256,94,760236,1,0 +298,111,760361,1,0 +323,145,760486,1,0 +328,187,760611,1,0 +311,225,760736,1,0 +370,298,760986,5,0 +308,368,761236,2,0,B|196:368|196:368,1,100,8|0 +176,280,761736,2,0,B|200:272|213:243|203:209|172:201,1,100,8|0 +256,152,762236,1,8 +336,204,762486,2,0,B|310:211|300:245|313:274|337:282,1,100,0|8 +344,60,763236,2,0,B|256:12|256:12|168:60,1,200,8|8 +169,139,763986,2,0,B|256:186|256:186|344:138,1,200 +256,100,764736,5,8 +256,260,765236,1,8 +120,344,765736,1,8 +256,260,766236,1,8 +392,344,766736,1,8 +446,187,767236,5,8 +385,127,767486,1,0 +368,40,767736,1,8 +296,100,767986,2,0,B|289:126|255:136|226:123|218:99,1,100,0|8 +143,40,768486,1,0 +126,127,768736,1,12 +65,192,768986,1,0 +126,256,769236,5,8 +144,343,769486,1,0 +218,284,769736,2,0,B|226:261|255:248|289:258|296:284,1,100,8|0 +368,343,770236,1,8 +385,256,770486,1,0 +446,192,770736,1,8 +376,38,771236,5,8 +300,84,771486,2,0,B|324:131|286:187|197:171|194:107|218:70,1,200 +135,38,772236,1,8 +100,120,772486,5,0 +40,52,772736,1,8 +57,216,773236,1,8 +181,324,773736,2,0,B|196:375|260:399|323:374|338:309,1,200,8|8 +454,216,774736,1,8 +472,52,775236,5,8 +412,120,775486,1,0 +376,38,775736,1,8 +300,200,776236,2,0,B|324:153|286:97|197:113|194:177|218:214,1,200,8|8 +328,320,777236,2,0,B|278:316|258:376|258:376|238:316|175:320,1,200,8|8 +100,120,778236,5,8 +40,52,778486,1,0 +135,38,778736,1,8 +337,193,779236,2,0,B|314:148|258:122|191:147|175:199,1,200,8|8 +177,284,779986,2,0,B|193:332|254:358|327:334|336:280,1,200 +256,240,780736,5,8 +340,56,781236,1,8 +172,56,781736,1,8 +256,104,781986,1,0 +340,56,782236,1,8 +352,228,782736,2,0,B|287:229|287:229|255:245|255:245|223:229|223:229|151:229,1,200,8|8 +360,352,783736,5,8 +256,384,783986,1,8 +152,352,784236,1,8 +173,256,784486,2,0,B|185:285|256:336|325:292|350:244,1,200,8|0 +256,196,785236,1,0 +309,100,785486,2,0,B|336:52|291:-27|183:-4|172:77|210:125,1,240.000005722046 +112,168,786236,1,0 +128,272,786486,6,0,B|168:232|213:263|213:263|256:289|303:260|303:260|356:231|392:283|392:283,1,279.99999332428 +400,367,787236,1,0 +400,367,787486,2,0,B|354:412|303:377|303:377|254:347|200:380|200:380|140:413|99:354|99:354,1,320 +92,204,788236,5,0 +92,204,788486,2,0,B|135:142|257:127|384:123|441:224|441:224,1,359.999982833863 +442,299,789236,1,0 +442,299,789486,2,0,B|390:365|256:382|119:384|55:280|55:280,1,400 +106,180,790236,5,0 +32,87,790486,1,0 +160,64,790736,1,0 +200,100,790861,1,0 +216,156,790986,1,0 +208,212,791111,1,0 +256,244,791236,1,0 +304,212,791361,1,0 +296,156,791486,1,0 +312,100,791611,1,0 +356,64,791736,1,0 +484,96,791986,5,0 +412,196,792236,1,0 +412,196,792361,1,0 +412,196,792486,1,0 +370,318,792736,2,0,B|340:361|262:401|173:370|137:311,1,259.999995350838,4|0 +316,168,793736,6,0,B|345:220|295:307|176:281|164:192|206:139,1,259.999995350838 +160,68,794486,1,0 +256,8,794736,1,0 +352,68,794986,1,0 +360,240,795486,2,0,B|402:240|402:240|442:240|458:272|458:296|458:296|458:320|442:352|402:352|402:352|354:352|354:352,1,259.999995350838 +256,320,796236,1,0 +152,352,796486,2,0,B|158:352|110:352|110:352|70:352|54:320|54:296|54:296|54:272|70:240|110:240|110:240|152:240,1,259.999995350838 +200,144,797236,6,0,B|256:176|256:176|312:144,1,129.999997675419 +312,48,797736,2,0,B|255:15|255:15|199:47,1,129.999997675419 +368,232,798486,2,0,B|254:297|254:297|142:232,1,259.999995350838 +88,320,799236,6,0,B|200:384,1,129.999997675419 +311,384,799736,2,0,B|424:320,1,129.999997675419 +475,229,800236,1,0 +437,131,800486,1,0 +334,121,800736,2,0,B|354:55|308:3|254:-3|182:5|140:90|200:154|254:187|254:187|312:220|421:280|335:420|211:416|164:335|177:272,1,779.999986052513,4|0 +72,290,802486,5,0 +13,204,802736,1,0 +143,53,803236,2,0,B|173:10|251:-30|340:1|376:60,1,259.999995350838 +370,149,803986,2,0,B|340:192|262:232|173:201|137:142,1,259.999995350838 +192,360,804986,6,0,B|336:360,1,129.999997675419 +324,268,805486,2,0,B|180:268,1,129.999997675419 +128,177,805986,2,0,B|408:177,1,259.999995350838 +206,47,806986,6,0,B|216:16|254:0|298:13|307:47,1,129.999997675419 +304,152,807486,1,0 +256,168,807611,1,0 +208,152,807736,1,0 +128,216,807986,1,0 +160,256,808111,1,0 +208,280,808236,1,0 +256,288,808361,1,0 +304,280,808486,1,0 +352,256,808611,1,0 +384,216,808736,1,4 +464,128,808986,6,0,B|496:256,1,129.999997675419 +320,352,809486,2,0,B|192:384,1,129.999997675419 +16,254,809986,2,0,B|48:128,1,129.999997675419 +197,23,810486,6,0,B|168:76|217:160|334:136|346:48|305:-4,1,259.999995350838 +416,64,811236,1,0 +370,149,811486,2,0,B|340:192|262:232|173:201|137:142,1,259.999995350838 +96,64,812236,1,0 +48,160,812486,2,0,B|96:272|224:320|288:320|416:272|464:160,1,519.999990701676 +512,256,813736,1,0 +512,256,813986,5,0 +256,368,814486,1,0 +0,256,814986,1,0 +0,256,815236,1,0 +72,176,815486,5,0 +104,216,815611,1,0 +110,268,815736,1,0 +208,312,815986,1,0 +256,296,816111,1,0 +304,312,816236,1,0 +402,268,816486,1,0 +408,216,816611,1,0 +440,176,816736,1,4 +360,120,816986,6,0,B|391:110|407:72|394:28|360:19,1,129.999997675419 +256,0,817486,2,0,B|256:128,1,129.999997675419 +152,120,817986,2,0,B|121:110|105:72|118:28|152:19,2,129.999997675419 +215,203,818736,1,0 +300,206,818986,2,0,B|341:261|305:341|181:326|184:237|218:200,1,259.999995350838 +352,352,819986,1,0 +256,384,820236,1,0 +160,352,820486,1,0 +92,159,820986,5,0 +149,69,821236,1,0 +256,36,821486,1,0 +362,69,821736,1,0 +419,159,821986,1,0 +349,362,822486,5,0 +307,353,822611,1,0 +273,328,822736,1,0 +238,302,822861,1,0 +213,260,822986,1,0 +196,226,823111,1,0 +187,183,823236,1,0 +187,140,823361,1,0 +213,106,823486,1,0 +256,89,823611,1,0 +298,106,823736,1,0 +324,140,823861,1,0 +324,183,823986,1,0 +315,226,824111,1,0 +298,260,824236,1,0 +273,302,824361,1,0 +238,328,824486,1,0 +204,353,824611,1,0 +162,362,824736,1,4 +40,240,824986,6,0,B|71:230|87:192|74:148|40:139,1,129.999997675419 +205,34,825486,2,0,B|215:65|253:81|297:68|306:34,1,129.999997675419 +472,144,825986,2,0,B|441:154|425:192|438:236|472:245,1,129.999997675419 +307,350,826486,2,0,B|297:319|259:303|215:316|206:350,1,129.999997675419 +128,56,827236,5,0 +384,56,827736,1,0 +148,348,828236,1,0 +256,296,828486,1,0 +364,348,828736,1,0 +24,104,829236,2,0,B|93:112|135:188|104:277|12:281,1,259.999995350838 +148,36,830236,5,0 +256,88,830486,1,0 +364,36,830736,1,0 +488,280,831236,2,0,B|419:272|377:196|408:107|500:103,1,259.999995350838 +306,327,832236,2,0,B|296:358|258:374|214:361|205:327,1,129.999997675419 +256,192,832736,5,4 +64,48,833236,1,0 +184,280,833736,5,0 +152,152,833986,1,0 +256,80,834236,1,0 +360,152,834486,1,0 +328,280,834736,1,4 +448,48,835236,5,0 +256,192,835736,1,0 +64,336,836236,5,0 +152,240,836486,2,0,B|121:230|105:192|118:148|152:139,1,129.999997675419 +360,240,837236,2,0,B|391:230|407:192|394:148|360:139,1,129.999997675419 +440,336,838236,1,0 +358,236,838486,2,0,B|305:220|265:252|257:292|257:292|249:252|205:218|151:239,1,259.999995350838 +72,336,839236,1,0 +24,216,839486,5,0 +72,96,839736,1,0 +200,32,839986,1,0 +184,80,840111,1,0 +208,128,840236,1,0 +256,144,840361,1,0 +304,128,840486,1,0 +328,80,840611,1,0 +312,32,840736,1,4 +440,96,840986,1,0 +488,216,841236,1,0 +390,300,841486,5,0 +341,342,841611,1,0 +328,280,841736,1,0 +184,280,841986,1,0 +171,342,842111,1,0 +122,300,842236,1,0 +87,178,842486,5,0 +195,106,842736,2,0,B|207:67|256:59|256:59|288:43|272:11|240:11|224:43|256:59|256:59|302:67|321:111,1,259.999995350838 +424,176,843486,1,0 +424,176,843611,1,0 +424,176,843736,1,0 +376,296,843986,1,0 +256,352,844236,1,0 +136,296,844486,1,0 +136,296,844611,1,0 +136,296,844736,1,4 +88,176,844986,1,0 +204,101,845236,2,0,B|214:70|252:54|296:67|305:101,1,129.999997675419 +384,352,845986,5,0 +360,304,846111,1,0 +304,288,846236,1,0 +256,320,846361,1,0 +208,288,846486,1,0 +152,304,846611,1,0 +128,352,846736,1,4 +208,192,846986,5,0 +256,208,847111,1,0 +304,192,847236,1,0 +400,128,847486,1,0 +416,80,847611,1,0 +384,32,847736,1,0 +328,24,847861,1,0 +288,56,847986,1,0 +256,88,848111,1,0 +224,56,848236,1,0 +184,24,848361,1,0 +128,32,848486,1,0 +96,80,848611,1,0 +112,128,848736,1,0 +152,248,848986,5,0 +152,248,849111,1,0 +152,248,849236,1,0 +144,376,849486,1,0 +144,376,849611,1,0 +144,376,849736,1,0 +256,320,849986,1,0 +256,320,850111,1,0 +256,320,850236,1,0 +368,376,850486,1,0 +368,376,850611,1,0 +368,376,850736,1,0 +360,248,850986,1,0 +360,248,851111,1,0 +360,248,851236,1,0 +256,192,851486,5,0 +256,192,851611,1,0 +256,192,851736,1,4 +256,320,851986,1,4 +95,186,852486,6,0,B|159:153|207:57|191:25|127:-7|95:25|63:89|223:153|255:81|255:81|287:25|255:-7|223:25|255:81|255:81|285:136|415:121|383:89|479:-39|301:-25|223:89|431:196|431:196,1,909.999983727932,4|2 +312,280,854486,2,0,B|298:259|259:239|214:254|196:284,1,129.999997675419,0|2 +256,381,854986,1,0 +370,173,855486,2,0,B|340:130|262:90|173:121|137:180,1,259.999995350838,2|0 +208,28,856236,1,2 +304,28,856486,1,0 +256,192,856611,12,0,858236 +256,192,859486,12,4,860736 +191,69,860986,6,0,B|204:43|254:19|302:36|324:74,1,149.999995529652,0|0 +448,172,861486,2,0,B|384:212|384:212|368:304,1,149.999995529652 +256,376,861986,1,0 +140,285,862236,2,0,B|128:212|128:212|64:172,1,149.999995529652 +169,47,862736,2,0,B|216:69|216:133|216:133|256:153|256:153|296:133|296:133|292:69|347:45,1,299.999991059304 +301,358,863736,1,0 +211,358,863986,2,0,B|157:296|201:201|345:218|341:321|296:365,1,299.999991059304 +73,188,864986,5,0 +138,89,865236,1,0 +256,48,865486,1,0 +373,89,865736,1,0 +438,188,865986,1,0 +178,345,866486,2,0,B|212:367|256:331|256:331|284:311|284:279|256:259|228:279|228:311|256:331|256:331|296:367|336:343,1,299.999991059304 +321,225,867236,2,0,B|308:199|258:175|210:192|188:230,1,149.999995529652 +96,148,867736,5,0 +128,108,867861,1,0 +164,80,867986,1,0 +212,64,868111,1,0 +256,56,868236,1,0 +300,64,868361,1,0 +348,80,868486,1,0 +384,108,868611,1,0 +416,148,868736,1,4 +256,224,868986,5,0 +208,364,869236,1,0 +328,276,869486,1,0 +184,276,869736,1,0 +304,364,869986,1,0 +256,224,870236,5,0 +128,112,870486,2,0,B|160:61|249:17|350:49|391:118,1,299.999991059304 +256,200,871236,1,0 +126,291,871486,2,0,B|158:341|247:386|348:354|389:284,1,299.999991059304 +256,200,872236,1,0 +113,156,872486,6,0,B|35:111|52:28|108:-8|156:-7|216:29|209:95|209:95|224:62|256:174|288:62|304:94|304:94|292:32|356:-16|412:-6|465:38|473:120|387:160,1,749.999977648259,0|0 +372,298,873986,2,0,B|311:279|266:316|256:361|256:361|247:316|197:277|136:301,1,299.999991059304 +256,20,874986,5,0 +128,116,875236,1,0 +184,260,875486,1,0 +328,260,875736,1,0 +384,116,875986,1,0 +256,156,876236,5,0 +256,156,876361,1,0 +256,156,876486,1,0 +256,156,876611,1,0 +256,156,876736,1,4 +256,308,876986,5,0 +400,356,877236,1,0 +488,236,877486,1,0 +400,116,877736,1,0 +256,156,877986,1,0 +256,308,878236,1,0 +112,364,878486,1,0 +24,236,878736,1,0 +112,116,878986,1,0 +408,64,879486,6,0,B|459:96|503:185|471:286|402:327,1,299.999991059304 +256,192,880236,1,0 +104,64,880486,2,0,B|53:96|9:185|41:286|110:327,1,299.999991059304 +322,237,881236,2,0,B|309:263|259:287|211:270|189:232,1,149.999995529652 +320,147,881736,2,0,B|307:121|257:97|209:114|187:152,1,149.999995529652 +388,304,882486,6,0,B|356:355|267:399|166:367|125:298,1,299.999991059304 +256,192,883236,1,0 +184,60,883486,1,0 +256,32,883611,1,0 +328,60,883736,1,0 +396,196,883986,1,0 +341,251,884111,1,0 +326,324,884236,1,0 +256,360,884361,1,0 +185,324,884486,1,0 +170,251,884611,1,0 +115,196,884736,1,4 +256,192,884861,12,0,886236 +128,128,933524,5,2 +214,104,933953,1,0 +152,42,934381,1,2 +65,65,934810,1,0 +42,152,935238,1,2 +104,214,935667,1,0 +190,190,936095,1,2 +212,228,936310,1,0 +256,244,936524,1,0 +300,228,936738,1,0 +323,189,936952,5,2 +408,170,937381,1,0 +470,232,937809,1,2 +447,319,938238,1,0 +360,342,938666,1,2 +298,280,939095,1,0 +323,189,939523,1,2 +384,256,939952,1,0 +160,112,940380,5,2 +212,38,940809,1,0 +300,38,941237,1,2 +352,112,941666,1,0 +292,181,942094,2,0,B|272:153|230:153|219:184|219:184,1,84.9999964535238,2|0 +256,260,942951,1,2 +212,288,943165,1,0 +228,340,943380,1,0 +280,340,943594,1,0 +300,292,943808,1,2 +396,256,944236,6,0,B|468:192,1,84.9999964535238,0|2 +416,32,945093,2,0,B|376:108,1,84.9999964535238,0|2 +256,48,945950,1,0 +135,107,946379,2,0,B|96:32,1,84.9999964535238,2|0 +52,199,947236,2,0,B|116:256,1,84.9999964535238,2|0 +292,328,948093,6,0,B|272:356|230:356|219:325|219:325,1,84.9999964535238,2|0 +324,208,948950,2,0,B|352:124,1,84.9999964535238,2|0 +188,208,949807,2,0,B|160:124,1,84.9999964535238,2|0 +256,64,950449,1,0 +256,64,950664,5,2 +400,160,951092,1,0 +340,324,951521,1,2 +172,324,951949,1,0 +112,160,952378,1,2 +256,64,952806,1,0 +256,200,953235,5,2 +188,328,953663,1,0 +256,356,953877,1,0 +324,328,954092,1,2 +428,228,954520,1,0 +392,92,954948,1,2 +256,40,955377,1,0 +120,92,955805,1,2 +84,228,956234,1,0 +220,208,956662,6,0,B|240:236|282:236|293:205|293:205,1,84.9999964535238,2|0 +292,352,957519,2,0,B|272:324|230:324|219:355|219:355,1,84.9999964535238,2|0 +124,244,958376,1,2 +256,140,958805,1,0 +392,244,959233,1,2 +412,80,959662,1,0 +256,264,960090,1,2 +100,80,960519,1,0 +100,80,960733,1,0 +100,80,960947,5,2 +256,348,961376,1,0 +412,80,961804,1,2 +100,304,962233,5,0 +256,36,962661,1,2 +412,304,963090,1,0 +191,224,963518,1,2 +256,276,963732,1,0 +320,224,963947,1,0 +294,150,964161,1,0 +217,150,964375,1,2 +256,360,964804,5,0 +88,256,965232,1,2 +160,64,965660,1,0 +352,64,966089,1,2 +424,256,966517,1,0 +256,192,966946,1,2 +256,192,967160,1,0 +256,192,967374,1,0 +256,192,967589,1,0 +256,192,967803,5,2 +220,352,968231,2,0,B|240:380|282:380|293:349|293:349,1,84.9999964535238,0|2 +364,197,969088,1,0 +396,120,969303,1,0 +148,197,969731,1,0 +116,120,969945,1,0 +256,32,970374,5,2 +256,200,970802,1,0 +256,368,971231,1,2 +196,300,971445,2,0,B|224:300|224:300|256:284|256:284|288:300|288:300|320:300,1,127.499994680286,0|2 +439,128,972516,2,0,B|452:96|439:66|439:66,1,50 +352,57,972945,6,0,B|398:112|337:186|274:153|255:116|255:116|237:84|198:18|65:115|168:182|168:182,1,400,2|2 +120,348,975087,5,0 +178,278,975301,2,0,B|193:324|256:356|318:324|334:278,1,200 +392,348,976372,1,2 +355,173,976801,2,0,B|335:114|257:75|179:114|160:173,1,250 +95,106,978086,5,2 +256,24,978515,1,0 +256,24,978729,1,0 +256,328,979372,1,2 +336,168,979800,2,0,B|312:128|256:112|256:112|200:128|176:168,1,200,0|0 +417,106,981300,5,0 +417,106,981514,1,2 +256,24,981943,1,0 +295,191,982371,2,0,B|282:170|254:162|254:162|226:170|214:191,1,100,2|0 +216,280,983014,2,0,B|232:280|232:280|248:272|248:272|256:280|256:280|256:264|256:264|264:272|264:272|280:280|280:280|296:280,1,100 +344,351,983657,2,0,B|320:383|280:383|256:359|256:359|232:383|192:383|168:351,1,200,2|0 +80,328,984728,5,0 +144,264,984942,1,2 +56,240,985156,1,0 +256,56,985799,1,0 +368,264,986227,1,0 +456,240,986442,1,0 +432,328,986656,1,2 +56,264,987513,6,0,B|102:249|134:186|102:124|56:108,1,200,0|2 +256,92,988798,1,0 +256,92,989013,2,0,B|256:292,1,200 +256,292,990084,1,2 +456,264,990512,2,0,B|410:249|378:186|410:124|456:108,1,200 +256,36,991798,1,2 +196,108,992012,2,0,B|224:108|224:108|256:124|256:124|288:108|288:108|320:108,1,127.499994680286 +346,348,993083,5,2 +256,312,993297,1,0 +168,349,993512,2,0,B|111:265|160:184|191:152|261:139|319:160|356:186|407:266|341:356,1,500 +217,61,996939,6,0,B|224:83|256:100|287:83|295:61,1,100,2|0 +168,192,997796,1,2 +335,302,998225,2,0,B|320:348|257:380|195:348|179:302,1,200,0|2 +344,192,999510,1,0 +188,56,999939,5,2 +256,100,1000153,1,0 +324,56,1000367,1,0 +424,200,1000796,2,0,B|464:228|464:228|480:284,1,100,0|2 +348,360,1001653,1,0 +220,204,1002081,2,0,B|197:237|216:270|229:282|257:288|280:279|295:269|315:237|289:201,1,200,2|0 +164,360,1003367,5,2 +88,200,1003795,2,0,B|48:228|48:228|32:284,1,100 +144,60,1004652,1,2 +194,232,1005081,2,0,B|208:276|208:276|256:300|256:300|304:276|304:276|320:228,1,200 +369,59,1006366,1,2 +448,224,1006795,6,0,B|419:326,1,100,0|2 +256,240,1007651,2,0,B|256:136,1,100,0|2 +64,224,1008508,2,0,B|93:326,1,100,0|2 +256,48,1009794,5,2 +400,152,1010222,1,0 +348,328,1010651,1,2 +164,328,1011079,1,0 +112,152,1011508,1,2 +256,48,1011936,1,0 +256,196,1012793,5,2 +256,348,1013864,5,0 +256,348,1014079,1,2 +256,52,1014507,1,0 +45,192,1015364,2,0,B|61:143|110:127|175:143|142:192|158:256|239:240|256:192|256:192|272:240|353:256|369:192|336:143|401:127|450:143|466:192,1,549.999983608723,2|0 +179,48,1022649,6,0,B|194:94|257:126|319:94|335:48,1,200,8|0 +340,239,1023506,2,0,B|301:254|270:239|255:208|255:208|240:239|209:254|171:239,1,200,8|0 +172,349,1024148,2,0,B|210:333|241:349|256:379|256:379|271:349|302:333|340:349,1,200,0|0 +432,296,1024791,1,0 +456,64,1025220,1,8 +256,152,1025648,5,0 +64,64,1026077,1,8 +112,152,1026291,1,0 +174,64,1026506,2,0,B|201:28|256:10|256:10|310:28|337:64,1,200,0|8 +424,256,1027363,5,0 +360,336,1027577,1,0 +256,368,1027791,1,8 +152,336,1028006,1,0 +88,256,1028220,1,0 +256,40,1028648,1,8 +424,256,1029077,1,0 +408,56,1029506,5,8 +345,355,1029934,1,0 +282,199,1030148,2,0,B|309:172|361:186|361:251|309:263|309:263|270:277|256:315|256:315|243:277|204:263|204:263|152:251|152:186|204:172|230:199,1,439.999986886979 +167,355,1031220,1,8 +73,146,1031648,2,0,B|75:82|134:16|220:36|257:81|257:81|289:32|366:25|442:61|436:157,1,500 +400,256,1032934,1,8 +221,364,1033363,2,0,B|198:331|217:298|230:286|258:280|281:289|296:299|316:331|290:367,1,200,0|8 +112,256,1034220,5,0 +96,144,1034434,1,0 +200,184,1034648,1,8 +312,184,1034863,1,0 +416,144,1035077,1,0 +400,256,1035291,1,0 +333,336,1035506,6,0,B|318:290|255:258|193:290|177:336,1,200,8|0 +96,256,1036148,1,0 +64,152,1036363,2,8,B|160:120|160:120|192:24,1,200,8|0 +320,26,1037220,2,0,B|352:120|352:120|448:152,1,200,8|0 +336,240,1037863,2,0,B|288:240|288:240|256:200|256:200|224:240|224:240|176:240,1,200 +256,320,1038506,1,0 +64,192,1038934,5,8 +256,48,1039363,1,0 +304,184,1039577,1,0 +180,96,1039791,1,8 +332,96,1040006,1,0 +208,184,1040220,1,0 +448,192,1040648,1,8 +216,336,1041077,2,0,B|222:356|256:370|290:356|297:336,1,100 +333,189,1041506,2,0,B|370:117|336:59|311:34|253:28|214:34|176:54|137:117|184:193,2,400,8|8|8 +348,360,1043648,5,0 +256,320,1043863,1,0 +164,360,1044077,2,0,B|118:376|53:357|33:292|50:246,1,200,8|0 +21,137,1044720,1,0 +131,108,1044934,1,8 +160,217,1045148,1,0 +160,216,1045363,2,0,B|256:172|256:260|352:216,1,200,0|8 +460,28,1046220,1,0 +460,28,1046648,6,0,B|480:76|460:124,1,100,8|0 +348,84,1047077,2,0,B|280:176|348:268,1,200,0|8 +356,368,1047720,1,0 +256,336,1047934,1,0 +156,368,1048148,1,0 +163,268,1048363,2,0,B|231:175|163:83,1,200,8|0 +4,192,1049220,5,8 +256,352,1049648,1,0 +256,352,1049863,1,0 +256,352,1050077,6,0,B|352:328,1,100,4|0 +476,252,1050506,2,0,B|380:228,1,100,2|0 +256,148,1050934,2,0,B|352:124,1,100,2|0 +476,48,1051363,2,0,B|380:24,1,100,2|0 +256,48,1051791,6,0,B|160:92|160:4|64:48,1,200,0|0 +100,144,1052434,1,0 +100,144,1052648,2,0,B|120:192|100:240,1,100 +36,320,1053077,2,0,B|129:388|221:320,1,200,0|4 +256,224,1053720,1,0 +291,320,1053934,2,0,B|383:388|476:320,1,200 +512,116,1054791,6,0,B|416:144,1,100 +328,100,1055220,2,0,B|260:192|328:284,1,200,8|0 +312,384,1055863,1,0 +228,332,1056077,2,0,B|200:284|192:240,1,100,8|0 +191,144,1056506,2,0,B|200:100|228:52,1,100 +256,192,1056934,6,0,B|160:236|160:148|64:192,1,200,8|0 +256,192,1057791,2,0,B|352:148|352:236|448:192,1,200,8|0 +492,104,1058434,5,0 +396,112,1058649,2,0,B|354:80|348:28,1,100,8|0 +256,92,1059077,2,0,B|256:192,1,100,0|0 +115,112,1059506,2,0,B|157:80|164:27,1,100,8|0 +20,104,1059934,6,0,B|-48:196|20:288,1,200,0|8 +72,372,1060577,1,0 +160,324,1060791,2,0,B|256:276|352:324,1,200,0|8 +488,192,1061649,6,0,B|508:96,1,100 +372,152,1062077,2,0,B|352:56,1,100,8|0 +256,164,1062506,2,0,B|210:148|145:167|125:232|142:278,1,200,0|8 +370,277,1063363,2,0,B|387:232|367:167|302:148|256:164,1,200,0|8 +136,128,1064006,5,0 +208,76,1064220,2,0,B|256:96|304:76,1,100 +376,128,1064649,2,0,B|476:144,1,100,8|0 +512,272,1065077,6,0,B|413:256,1,100 +348,332,1065506,2,0,B|256:400|164:332,1,200,8|0 +164,52,1066363,2,0,B|256:-16|348:52,1,200,8|0 +356,152,1067006,1,0 +256,192,1067220,1,4 +156,152,1067434,1,0 +160,280,1067649,6,0,B|256:324|256:236|352:280,2,200,0|8|0 +68,320,1068720,1,0 +156,368,1068934,6,0,B|204:388|252:368,1,100,8|0 +356,192,1069363,2,0,B|308:172|260:192,1,100 +258,192,1069791,1,4 +100,276,1070220,1,0 +28,44,1070649,5,4 +120,88,1070863,1,0 +220,84,1071077,2,0,B|320:76,1,100 +412,32,1071506,2,0,B|442:133|378:220,1,200 +424,308,1072149,1,0 +328,340,1072363,1,0 +244,288,1072577,2,0,B|200:316|184:368,1,100 +88,340,1073006,1,0 +120,244,1073220,2,0,B|29:183|23:77,1,200 +112,28,1073863,1,0 +200,80,1074077,6,0,B|202:131|167:170,1,100 +116,260,1074506,2,0,B|216:280,1,100 +297,279,1074934,2,0,B|396:260,1,100 +345,171,1075363,2,0,B|309:131|312:80,1,100 +404,29,1075791,5,0 +481,103,1076006,1,0 +486,209,1076220,1,0 +445,308,1076434,1,0 +348,353,1076649,2,0,B|256:285|164:353,1,200,0|0 +70,309,1077291,1,0 +52,220,1077506,6,0,B|12:191|1:121|51:72|100:71,1,200,8|2 +186,99,1078149,2,0,B|225:127|236:197|186:246|137:247,1,200,0|0 +204,324,1078791,1,2 +304,324,1079006,1,0 +374,247,1079220,2,0,B|325:246|275:197|286:127|326:99,1,200,8|2 +411,71,1079863,2,0,B|460:72|510:121|499:191|460:220,1,200,0|0 +452,320,1080506,1,2 +360,356,1080720,1,0 +300,276,1080934,1,8 +368,204,1081149,1,0 +375,94,1081363,6,0,B|345:34|301:49|256:20|183:94|183:167|256:212|330:167|330:94|256:20|212:49|168:34|138:94,1,439.999986886979,0|2 +144,204,1082434,1,0 +212,276,1082649,1,8 +152,356,1082863,1,0 +60,320,1083077,1,2 +256,368,1083506,5,8 +344,184,1083934,1,0 +256,136,1084149,1,0 +168,184,1084363,1,8 +334,320,1084791,6,0,B|319:274|256:242|194:274|178:320,1,200,0|8 +88,192,1085434,1,0 +178,64,1085649,2,0,B|193:110|256:142|318:110|334:64,1,200,0|8 +424,192,1086291,1,0 +256,328,1086506,5,0 +96,80,1086934,1,8 +416,80,1087363,1,0 +256,194,1087577,1,0 +126,330,1087791,2,0,B|66:314|68:260|85:226|136:209|170:243|170:243|136:174|170:123|187:89|254:60|324:89|341:123|375:174|341:243|341:243|385:204|452:231|448:316|387:332,1,800,8|8 +432,144,1089720,1,8 +256,192,1089934,1,8 +80,144,1090149,1,0 +256,304,1090363,5,8 +200,120,1090577,1,8 +344,232,1090791,1,0 +168,232,1091006,1,8 +312,120,1091220,1,4 +114,294,1091649,6,0,B|68:279|36:216|68:154|114:138,1,200,2|8 +160,48,1092291,1,0 +256,80,1092506,1,2 +352,48,1092720,1,0 +398,138,1092934,2,0,B|444:154|476:216|444:279|398:294,1,200,8|0 +345,359,1093577,2,0,B|276:403|203:333|201:270|252:276|303:269|300:333|221:403|159:358,1,329.999990165234,2|8 +96,288,1094434,1,0 +176,224,1094649,2,0,B|194:180|256:154|319:178|340:230,1,200,2|0 +416,288,1095291,1,0 +337,343,1095506,6,0,B|319:387|257:413|194:389|173:337,1,200,8|2 +256,292,1096149,1,0 +205,208,1096363,1,8 +306,208,1096577,1,0 +376,130,1096791,2,0,B|380:64|353:-4|258:-45|163:0|124:60|137:130,1,400,2|2 +64,200,1097863,5,0 +64,200,1098077,1,8 +216,360,1098506,2,0,B|200:332|211:298|231:286|256:274|280:285|300:295|321:325|297:370,1,200,0|8 +448,200,1099363,1,0 +297,32,1099791,2,0,B|313:60|302:94|282:106|257:118|233:107|213:97|192:67|216:22,2,200,2|0|8 +156,200,1101077,5,2 +179,297,1101291,1,0 +272,322,1101506,1,10 +341,249,1101720,1,0 +317,152,1101934,1,0 +220,128,1102149,1,0 +146,223,1102363,1,8 +193,321,1102577,1,0 +298,327,1102791,1,0 +355,233,1103006,1,0 +308,134,1103220,5,8 +203,128,1103434,1,8 +149,253,1103649,1,8 +227,345,1103863,1,0 +342,321,1104077,1,8 +376,205,1104291,1,0 +297,113,1104506,1,8 +183,137,1104720,1,0 +147,263,1104934,5,12 +327,301,1105363,1,0 +365,121,1105791,1,8 +185,83,1106220,6,0,B|144:55|77:57|40:114|45:163,1,200,0|8 +76,260,1106863,1,0 +160,192,1107077,2,0,B|256:236|256:148|352:192,1,200,0|8 +436,124,1107720,5,0 +466,221,1107934,2,0,B|471:269|434:326|367:328|327:301,1,200,0|8 +256,232,1108577,1,0 +172,176,1108791,6,0,B|152:128|172:80,1,100 +340,208,1109220,2,0,B|360:256|340:304,1,100,8|0 +163,360,1109649,6,0,B|118:376|53:357|33:292|49:246,1,200,0|8 +16,152,1110291,1,0 +108,116,1110506,2,0,B|84:20,1,100 +224,76,1110934,2,0,B|248:173,1,100,8|0 +336,128,1111363,6,0,B|440:173|428:287,1,200,0|8 +84,287,1112220,2,0,B|71:173|176:128,1,200,0|8 +352,348,1113077,6,0,B|256:304|256:392|160:348,1,200,0|8 +112,276,1113720,1,0 +208,260,1113934,2,0,B|188:212|208:164,1,100 +304,164,1114363,2,0,B|324:212|304:260,1,100,8|0 +128,192,1114791,2,0,B|132:124|160:60|256:28|352:60|380:124|384:192,1,400,0|0 +332,328,1116077,1,8 +180,56,1116506,1,0 +372,12,1116934,6,0,B|440:104|372:196,1,200,8|0 +140,372,1117791,2,0,B|72:280|140:188,1,200,8|0 +184,96,1118434,1,0 +300,108,1118649,6,0,B|300:192|212:192|212:280,2,200,4|0|8 +256,20,1119720,1,0 +212,108,1119934,2,0,B|212:192|300:192|300:280,1,200,0|8 +256,364,1120577,1,0 +163,360,1120791,6,0,B|118:376|53:357|33:292|49:246,1,200,0|8 +348,360,1121649,2,0,B|394:376|459:357|479:292|462:246,1,200,0|4 +452,148,1122291,5,0 +356,124,1122506,2,0,B|311:153|308:207,1,100 +352,280,1122934,2,0,B|304:304|256:280|256:280|208:304|160:280,1,200,8|0 +204,207,1123577,2,0,B|200:153|156:124,1,100,0|8 +60,148,1124006,1,8 +60,148,1124220,6,0,B|72:48,1,100 +160,100,1124649,2,0,B|208:124|256:100|256:100|304:76|352:100,1,200,8|0 +440,48,1125291,1,0 +440,48,1125506,6,0,B|452:148,1,100,8|0 +376,268,1125934,2,0,B|364:168,1,100,8|0 +456,304,1126363,6,0,B|436:348|376:380|316:348|296:304,1,200,8|0 +256,192,1127006,1,0 +147,168,1127220,2,0,B|136:268,1,100,8|8 +55,303,1127649,2,0,B|76:348|136:380|196:348|216:303,2,200,8|4|0 +64,220,1128720,1,0 +111,151,1128934,6,0,B|88:80,1,74.9999977648259 +208,32,1129363,2,0,B|224:68|208:104,1,74.9999977648259 +304,156,1129791,2,0,B|288:120|304:84,1,74.9999977648259 +424,80,1130220,2,0,B|400:151,1,74.9999977648259 +482,201,1130649,5,0 +458,275,1130863,1,0 +397,322,1131077,1,0 +320,325,1131291,1,0 +256,284,1131506,1,0 +192,325,1131720,1,0 +115,322,1131934,1,0 +54,275,1132149,1,0 +30,201,1132363,1,0 +256,192,1132470,12,2,1135791 +80,56,1136220,5,0 +212,252,1136649,2,0,B|196:224|207:190|227:178|252:166|276:177|296:187|317:217|293:262,1,200,2|0 +432,56,1137505,1,2 +369,247,1137949,2,0,B|379:196|354:120|287:88|223:88|161:122|127:184|140:247,1,400,0|2 +68,288,1139060,1,0 +48,200,1139282,5,2 +256,224,1139743,1,0 +464,200,1140205,1,2 +346,34,1140776,2,0,B|307:69|273:52|256:35|256:35|238:52|204:69|164:31,1,200,0|2 +256,320,1153040,5,8 +256,252,1153343,1,8 +256,240,1153494,1,8 +256,228,1153646,1,8 +256,216,1153797,1,0 +256,204,1153949,1,4 +380,136,1154252,6,0,B|412:196,1,66.6666666666667,0|8 +456,124,1154555,2,0,B|423:62,1,66.6666666666667,0|0 +324,64,1154858,2,2,B|256:92|188:64,1,133.333333333333,2|0 +88,62,1155312,2,0,B|56:124,1,66.6666666666667,8|0 +99,197,1155616,2,0,B|132:136,1,66.6666666666667,0|0 +256,228,1156070,6,0,B|256:296,2,66.6666666666667,0|8|0 +256,140,1156525,1,0 +196,80,1156676,2,2,B|256:53|256:53|316:80,1,133.333333333333,2|0 +388,140,1157131,2,0,B|456:156,2,66.6666666666667,8|0|0 +316,201,1157585,6,0,B|255:227|255:227|195:201,1,133.333333333333,4|0 +124,140,1158040,2,0,B|56:156,1,66.6666666666667,8|0 +44,336,1158494,2,2,B|184:304,1,133.333333333333,2|0 +256,256,1158949,2,0,B|256:324,1,66.6666666666667,8|0 +468,336,1159403,6,0,B|328:304,1,133.333333333333,4|0 +324,212,1159858,2,0,B|360:152,1,66.6666666666667,8|0 +256,140,1160161,1,0 +188,212,1160312,2,0,B|152:152,1,66.6666666666667,2|0 +204,52,1160616,5,8 +228,40,1160767,1,8 +256,36,1160919,1,8 +288,40,1161070,1,0 +316,52,1161222,1,4 +468,156,1161525,6,0,B|468:224,1,66.6666666666667,0|8 +468,316,1161828,1,0 +380,344,1161979,1,0 +288,352,1162131,2,0,B|288:284|288:284|336:228,1,133.333333333333,2|0 +248,192,1162585,6,0,B|200:248,1,66.6666666666667,8|0 +140,312,1162888,1,0 +80,244,1163040,2,0,B|176:140,1,133.333333333333,0|0 +256,100,1163494,2,0,B|256:30,2,66.6666666666667,8|0|0 +337,141,1163949,2,0,B|432:244,1,133.333333333333,2|0 +339,268,1164403,2,0,B|305:222|256:203|206:222|172:268,1,200,8|4 +172,93,1165161,5,0 +171,92,1165312,2,0,B|205:137|256:156|305:137|339:92,1,200,8|2 +358,254,1166070,2,0,B|332:320,1,66.6666666666667,0|8 +256,360,1166373,1,0 +179,319,1166525,2,0,B|154:254,1,66.6666666666667,0|4 +316,112,1166979,6,0,B|304:44,2,66.6666666666667,8|8|8 +256,180,1167434,1,0 +196,112,1167585,2,8,B|208:44,2,66.6666666666667,8|8|0 +152,224,1168040,6,0,B|216:252,1,66.6666666666667,8|8 +360,224,1168343,2,0,B|296:252,1,66.6666666666667,0|4 +256,32,1168949,5,8 +420,112,1169252,1,0 +494,80,1169403,2,2,B|462:12|375:0|319:68|343:144|343:144|356:188|356:188|379:259|323:287,1,400,2|0 +144,268,1170616,2,0,B|136:232|144:200,1,66.6666666666667,0|8 +172,120,1170919,1,0 +224,56,1171070,2,0,B|256:32|288:56,1,66.6666666666667,0|2 +340,120,1171373,1,0 +367,199,1171525,2,0,B|376:232|368:268,1,66.6666666666667,0|8 +187,287,1172131,6,0,B|132:260|154:188|154:188|168:144|168:144|192:68|136:0|48:12|16:80,1,400,4|2 +24,164,1173191,1,0 +92,112,1173343,1,0 +164,68,1173494,2,0,B|196:48|232:60|259:88|259:88|283:112,1,133.333333333333,8|0 +328,184,1173949,1,2 +256,224,1174100,1,0 +184,184,1174252,1,0 +227,112,1174403,2,0,B|252:88|252:88|280:60|316:48|348:68,1,133.333333333333,8|0 +452,192,1174858,5,4 +256,356,1175161,1,2 +60,192,1175464,1,2 +256,28,1175767,1,4 +256,196,1176070,6,0,B|292:136,1,66.6666666666667,0|8 +256,196,1176373,2,0,B|220:136,1,66.6666666666667 +174,68,1176676,2,0,B|99:68|51:153|99:239|174:239,1,266.666666666667,4|2 +338,67,1177585,2,0,B|412:67|461:152|412:239|338:239,1,266.666666666667,4|2 +192,152,1178494,2,0,B|228:184|256:152|284:120|320:152,2,133.333333333333,4|2|2 +80,281,1179403,6,0,B|136:352|256:396|376:352|441:272,1,400,4|2 +448,188,1180464,1,0 +368,164,1180616,1,0 +356,248,1180767,2,0,B|332:272|292:276,1,66.6666666666667,8|0 +156,248,1181070,6,0,B|180:272|218:275,1,66.6666666666667,8|4 +212,192,1181373,2,0,B|188:168|150:165,1,66.6666666666667,0|0 +68,148,1181676,1,8 +84,64,1181828,1,8 +168,80,1181979,1,8 +256,106,1182131,6,0,B|256:36,1,66.6666666666667 +344,80,1182434,1,0 +428,64,1182585,1,8 +444,148,1182737,1,8 +362,164,1182888,2,0,B|324:168|300:192,1,66.6666666666667,0|4 +68,292,1183494,5,8 +40,212,1183646,2,0,B|84:192|140:220|148:268,2,133.333333333333,0|2|0 +32,128,1184403,1,8 +116,136,1184555,2,0,B|188:152|216:228,1,133.333333333333,0|4 +395,136,1185161,6,0,B|324:152|296:225,1,133.333333333333,0|0 +408,352,1185767,2,0,B|479:336|507:262,1,133.333333333333,2|0 +396,136,1186373,1,0 +104,352,1186676,6,0,B|32:336|4:262,1,133.333333333333,4|0 +80,220,1187131,1,8 +164,204,1187282,2,0,B|148:136,1,66.6666666666667 +192,69,1187585,2,0,B|256:87|256:87|319:69,1,133.333333333333,2|0 +364,135,1188040,2,0,B|348:204,1,66.6666666666667,8|0 +256,344,1188494,5,4 +140,120,1188797,1,2 +332,284,1189100,1,2 +256,44,1189403,1,4 +180,284,1189706,1,2 +372,120,1190009,1,2 +256,344,1190312,1,4 +488,244,1190767,6,0,B|436:40,1,200,8|2 +300,140,1191525,2,0,B|364:164,1,66.6666666666667,0|8 +212,140,1191979,2,0,B|148:164,1,66.6666666666667,8|2 +75,40,1192585,2,0,B|24:244,1,200,8|2 +204,268,1193343,5,0 +228,280,1193494,1,8 +256,284,1193646,1,8 +288,280,1193797,1,0 +316,268,1193949,1,4 +80,203,1194403,6,0,B|141:127|256:85|370:127|431:203,1,400,8|10 +212,312,1195767,2,0,B|180:252,1,66.6666666666667 +300,312,1196222,2,0,B|332:248,1,66.6666666666667,8|0 +212,144,1196676,5,8 +208,108,1196828,1,0 +224,76,1196979,1,0 +256,64,1197131,1,8 +288,76,1197282,1,0 +304,108,1197434,1,0 +300,144,1197585,1,12 +392,264,1197888,5,2 +392,264,1198040,1,2 +256,344,1198343,1,2 +256,344,1198494,1,0 +120,264,1198797,1,2 +176,104,1199100,1,2 +332,104,1199403,1,0 +256,216,1199706,5,0 +256,200,1199858,1,8 +256,184,1200009,1,0 +256,168,1200161,1,0 +256,152,1200312,1,2 +304,284,1200616,1,0 +284,304,1200767,1,8 +256,312,1200919,1,0 +228,304,1201070,1,0 +208,284,1201222,1,2 +84,192,1201525,6,0,B|52:280|52:280,1,66.6666666666667,0|8 +32,108,1201979,2,0,B|64:32|64:32,1,66.6666666666667,0|2 +201,101,1202434,2,0,B|219:71|252:59|293:64|315:108,1,133.333333333333 +313,250,1203040,2,0,B|296:278|260:291|216:285|192:238,1,133.333333333333,2|0 +155,317,1203494,6,0,B|182:349|233:356,1,66.6666666666667,8|0 +298,353,1203797,2,0,B|332:345|357:317,1,66.6666666666667,0|2 +256,188,1204252,1,0 +256,168,1204403,1,8 +256,148,1204555,1,0 +256,128,1204706,1,0 +256,104,1204858,1,4 +428,192,1205161,6,0,B|460:280|460:280,1,66.6666666666667,0|8 +480,108,1205616,2,0,B|448:32|448:32,1,66.6666666666667,0|2 +311,57,1206070,2,0,B|292:30|265:30|254:19|254:19|246:30|220:30|203:55,1,133.333333333333 +121,176,1206676,2,0,B|130:226|215:249|256:199|256:199|288:247|388:235|392:162,1,333.333333333333,2|0 +464,232,1207585,1,2 +337,345,1207888,5,0 +294,354,1208040,1,8 +256,336,1208191,1,0 +217,354,1208343,1,0 +178,345,1208494,1,2 +48,232,1208797,1,0 +48,232,1208949,1,8 +224,165,1209252,6,0,B|256:176|256:176|290:164,1,66.6666666666667,0|2 +197,56,1209706,2,0,B|255:28|255:28|317:55,2,133.333333333333,0|0|2 +116,184,1210616,1,0 +147,197,1210767,1,8 +169,224,1210919,1,0 +181,256,1211070,1,0 +180,292,1211222,1,2 +332,292,1211525,1,8 +331,256,1211676,1,8 +343,224,1211828,1,8 +365,197,1211979,1,0 +396,184,1212131,1,4 +336,44,1212434,6,0,B|336:77|336:77|314:106,1,66.6666666666667,0|8 +176,44,1212888,2,0,B|175:77|175:77|197:106,1,66.6666666666667,0|2 +200,244,1213343,2,0,B|217:272|253:285|297:279|321:232,1,133.333333333333 +99,267,1213949,2,0,B|131:363|263:412|385:368|423:243,1,400,2|2 +180,145,1215312,6,0,B|195:97|255:86|255:86|311:94|333:148,1,200,8|4 +124,44,1216222,5,8 +256,168,1216525,1,0 +256,168,1216676,1,2 +388,44,1216979,1,0 +435,226,1217282,1,2 +353,350,1217585,2,0,B|284:340|117:251|74:123|147:-32|400:-9|466:163|336:289|227:339|160:351,1,800,2|4 +77,224,1219706,1,0 +77,224,1219858,5,8 +120,48,1220161,1,2 +120,48,1220312,1,2 +256,120,1220616,1,0 +392,48,1220919,1,0 +400,200,1221222,2,0,B|382:282|318:334|255:334|166:330|116:273|107:181|107:181,2,400,2|2|4 +256,40,1223494,5,8 +256,184,1223797,1,2 +256,184,1223949,1,2 +319,332,1224252,2,0,B|300:360|264:373|220:367|196:320,1,133.333333333333,0|0 +96,200,1224858,1,2 +184,72,1225161,5,0 +256,40,1225312,1,8 +328,72,1225464,1,0 +416,200,1225767,1,0 +296,356,1226070,1,8 +320,280,1226222,1,8 +256,232,1226373,1,0 +192,280,1226525,1,0 +216,356,1226676,1,4 +336,111,1227131,6,0,B|352:47|288:15|256:15|223:15|159:47|175:111,2,274.000001469851,2|2|8 +256,304,1228343,1,0 +193,277,1228494,2,0,B|181:298|174:343|209:397|268:399|316:376|345:340|324:276|318:268,1,274.000001469851,2|8 +496,128,1229403,1,0 +256,32,1229706,5,0 +216,156,1229858,1,8 +320,80,1230009,1,8 +192,80,1230161,1,8 +296,156,1230312,1,4 +16,128,1230767,5,2 +175,288,1231222,2,0,B|159:352|223:384|256:384|288:384|352:352|336:288,1,274.000001469851,2|8 +438,116,1231979,1,0 +361,51,1232131,2,0,B|334:77|294:90|255:51|255:24|255:24|255:51|215:90|175:77|149:51,1,274.000001469851,2|8 +64,336,1233040,1,2 +256,224,1233343,5,0 +296,356,1233494,1,8 +192,276,1233646,1,8 +320,276,1233797,1,0 +216,356,1233949,1,4 +362,100,1234403,6,0,B|338:31|253:0|175:28|151:108,1,274.000001469851,2|2 +448,336,1235312,1,8 +256,212,1235616,1,0 +203,281,1235767,2,0,B|184:332|184:332|220:332|256:368|256:368|292:332|328:332|328:332|308:279,1,274.000001469851,2|8 +64,337,1236676,1,2 +180,88,1237131,6,0,B|203:42|256:31|310:43|333:90,1,182.666667646567,8|0 +362,187,1237585,2,0,B|324:295|164:293|148:177|148:177,3,274.000001469851,4|8|2|8 +40,360,1239252,1,8 +127,312,1239403,2,0,B|166:350|217:355,1,91.3333338232835,2|0 +305,353,1239706,2,0,B|346:350|385:312,1,91.3333338232835,0|8 +472,360,1240009,1,2 +474,357,1240085,1,0 +476,354,1240161,5,8 +488,248,1240312,1,0 +408,208,1240464,1,0 +320,144,1240616,1,0 +256,64,1240767,1,8 +192,144,1240919,1,0 +112,208,1241070,1,0 +24,248,1241222,1,4 +328,340,1241676,6,0,B|320:312|296:280|256:268|216:280|192:312|184:340,1,196.000001869202,8|0 +120,252,1242131,2,0,B|180:208|256:180|332:208|392:252,1,294.000002803803,2|8 +472,96,1242888,1,0 +392,36,1243040,6,0,B|332:80|256:108|180:80|120:36,1,294.000002803803,4|8 +16,68,1243646,2,0,B|8:168,1,98.0000009346008 +112,140,1243949,2,0,B|196:196|204:308,1,196.000001869202,2|0 +308,305,1244403,2,0,B|316:196|400:140,1,196.000001869202,8|2 +368,36,1244858,6,0,B|304:60|256:128|256:128|208:60|144:36,2,294.000002803803,4|8|2 +503,165,1246070,2,0,B|496:68,1,98.0000009346008,0|8 +424,148,1246373,2,0,B|431:245,1,98.0000009346008,0|0 +340,300,1246676,6,0,B|340:224|292:192|256:192|220:192|172:160|172:84,1,294.000002803803,4|8 +339,85,1247585,2,0,B|340:160|292:192|256:192|220:192|172:224|171:299,1,294.000002803803,2|8 +80,356,1248191,2,0,B|68:304|76:260,1,98.0000009346008,8|0 +136,168,1248494,6,0,B|212:168|228:256|304:256,1,196.000001869202,4|0 +412,248,1248949,2,0,B|480:252|496:180|496:180|512:104,1,196.000001869202,8|0 +424,40,1249403,2,0,B|356:36|340:108|340:108|324:184,1,196.000001869202,2|0 +324,184,1249858,1,8 +100,248,1250161,5,0 +100,248,1250312,2,0,B|32:252|16:180|16:180|0:104,1,196.000001869202,4|0 +88,40,1250767,2,0,B|156:36|172:108|172:108|188:184,1,196.000001869202,8|0 +292,212,1251222,2,0,B|384:220|384:220|396:164|372:120,1,196.000001869202,2|0 +324,60,1251676,1,8 +256,28,1251828,1,0 +188,60,1251979,1,0 +140,120,1252131,6,0,B|116:164|128:220|128:220|220:212,1,196.000001869202,4|0 +328,204,1252585,2,0,B|384:200|384:200|440:196|472:248|452:308|392:312|392:312|340:316,2,294.000002803803,8|2|8 +328,204,1253646,1,0 +174,316,1253949,2,0,B|120:312|120:312|60:308|40:248|72:196|128:200|128:200|184:204,1,294.000002803803,4|8 +192,96,1254555,2,0,B|96:88,2,98.0000009346008,0|8|4 +418,87,1255161,6,0,B|320:96,1,98.0000009346008,0|8 +280,184,1255464,2,0,B|378:175,1,98.0000009346008,8|0 +376,272,1255767,6,0,B|280:280|280:280|224:284|204:336,1,196.000001869202,4|0 +308,336,1256222,2,0,B|288:284|232:280|232:280|136:272,1,196.000001869202,8|0 +40,264,1256676,2,0,B|52:180|140:176|140:176|228:172|240:88,1,294.000002803803,2|8 +144,80,1257282,1,0 +136,272,1257585,5,4 +256,116,1257888,1,0 +256,116,1258040,1,8 +376,272,1258343,1,0 +376,272,1258494,2,0,B|424:252|460:200|440:136|416:108,1,196.000001869202,2|0 +332,60,1258949,2,0,B|280:60|256:8|256:8|232:60|180:60,1,196.000001869202,8|0 +96,108,1259403,2,0,B|71:136|51:200|87:252|135:272,1,196.000001869202,4|0 +180,360,1259858,2,0,B|232:360|256:308|256:308|280:360|332:360,1,196.000001869202,8|0 +424,320,1260312,2,0,B|368:276|336:192|368:108|424:64,2,294.000002803803,2|8|4 +256,176,1261676,5,8 +87,320,1262131,2,0,B|144:276|176:192|144:108|88:64,1,294.000002803803,2|8 +32,144,1262737,1,2 +32,240,1262888,1,0 +132,236,1263040,6,0,B|328:228,1,196.000001869202,4|0 +380,148,1263494,2,0,B|184:156,1,196.000001869202,8|0 +184,156,1263949,1,2 +332,60,1264252,2,0,B|429:55,1,98.0000009346008,0|8 +436,232,1264706,2,0,B|338:236,1,98.0000009346008,0|4 +82,329,1265161,6,0,B|180:324,1,98.0000009346008,0|8 +173,148,1265616,2,0,B|76:152,1,98.0000009346008,0|2 +132,236,1266070,1,0 +132,236,1266222,2,0,B|328:228,1,196.000001869202,8|0 +432,192,1266676,5,4 +256,128,1266979,1,0 +256,128,1267131,1,8 +80,192,1267434,1,0 +80,192,1267585,1,2 +352,240,1267888,1,0 +352,240,1268040,1,8 +160,240,1268343,1,0 +160,240,1268494,1,2 +316,372,1268949,5,8 +256,380,1269100,1,8 +196,372,1269252,1,2 +168,288,1269403,5,8 +256,276,1269555,1,0 +344,284,1269706,1,2 +372,200,1269858,5,8 +256,176,1270009,1,0 +140,200,1270161,1,2 +256,100,1270312,5,4 +384,280,1270767,6,0,B|436:280|488:252|512:192|488:132|436:104|384:104,1,294.000002803803,8|2 +300,126,1271373,2,0,B|300:224,1,98.0000009346008,0|2 +256,296,1271676,1,8 +212,224,1271828,2,0,B|212:126,1,98.0000009346008,0|2 +128,103,1272131,2,0,B|76:104|24:132|0:192|24:252|76:280|128:280,3,294.000002803803,4|2|0|2 +340,76,1273949,6,0,B|384:108|412:184|384:260|332:308|256:328|180:308|128:260|100:184|128:108|172:76,2,588.000005607605,4|2|4 +48,40,1276222,1,8 +36,136,1276373,1,0 +72,228,1276525,1,0 +140,296,1276676,2,0,B|220:304|256:224|256:224|292:304|372:296,1,294.000002803803,2|8 +440,228,1277282,1,0 +476,136,1277434,1,0 +464,40,1277585,1,4 +256,248,1278040,5,8 +256,64,1278494,1,2 +160,164,1278646,5,0 +160,260,1278797,1,0 +256,288,1278949,1,8 +352,260,1279100,1,0 +352,164,1279252,1,2 +256,136,1279403,1,4 +456,56,1279858,6,0,B|456:350,1,294.000002803803,8|2 +56,350,1280767,2,0,B|56:56,1,294.000002803803,8|4 +360,88,1281676,2,0,B|360:212|360:212|360:284|328:324|256:352|184:324|152:284|152:212|152:212|152:88,1,588.000005607605,8|8 +20,284,1283040,5,4 +8,208,1283191,1,0 +16,128,1283343,1,2 +80,80,1283494,1,8 +160,80,1283646,1,0 +228,120,1283797,1,2 +256,192,1283949,1,8 +284,264,1284100,1,0 +352,304,1284252,1,0 +432,304,1284403,1,8 +496,256,1284555,1,8 +504,176,1284706,1,0 +492,100,1284858,1,4 +208,192,1291222,5,0 +148,112,1291373,1,0 +148,112,1291449,1,0 +148,112,1291525,1,0 +52,132,1291676,1,0 +32,228,1291828,1,0 +100,292,1291979,1,0 +184,281,1292131,6,0,B|205:317|263:349|322:311|334:277,1,178.666658215332,4|2 +309,192,1292585,2,0,B|301:140|257:124|257:124|205:104|209:48,1,183.333327869574,8|0 +305,56,1293040,2,0,B|309:104|257:124|257:124|213:140|201:196,2,183.333327869574,2|0|0 +392,40,1293797,1,0 +464,96,1293949,1,2 +392,264,1294252,5,0 +336,328,1294403,1,8 +256,352,1294555,1,0 +176,328,1294706,1,0 +120,264,1294858,1,2 +120,120,1295161,1,0 +176,56,1295312,1,8 +256,32,1295464,1,0 +336,56,1295616,1,0 +392,120,1295767,1,2 +256,200,1296070,5,0 +256,200,1296222,1,8 +256,24,1296525,5,0 +256,24,1296676,1,2 +201,169,1296979,1,0 +331,76,1297282,1,2 +175,76,1297585,1,0 +297,171,1297888,1,0 +300,259,1298040,6,0,B|255:244|255:244|212:259,1,91.6666639347872,8|0 +212,349,1298343,2,0,B|256:363|256:363|299:349,1,91.6666639347872 +392,184,1298797,1,8 +346,101,1298949,1,8 +256,67,1299100,1,0 +165,101,1299252,1,8 +119,184,1299403,1,4 +256,276,1299706,5,0 +182,318,1299858,2,0,B|202:359|257:382|312:360|331:316,1,183.333327869574,2|0 +332,233,1300312,2,0,B|310:192|258:168|200:192|183:234,2,183.333327869574,2|0|0 +422,128,1301222,2,0,B|352:92|336:12|336:12|256:108|256:108|180:8|180:8|156:92|94:128,2,549.999983608723,2|2|4 +188,308,1303494,6,0,B|201:351|256:361|256:361|307:354|327:304,1,183.333327869574,8|0 +256,256,1303949,1,2 +360,80,1304252,1,0 +152,80,1304555,1,2 +68,233,1304858,2,0,B|52:304|140:352|191:289|191:289|209:334|255:347|302:334|320:291|320:291|370:351|463:307|443:225,2,549.999983608723,0|2|4 +256,68,1306979,5,0 +256,68,1307131,1,8 +256,252,1307434,1,0 +182,199,1307585,2,0,B|203:199|203:199|228:176|228:176|256:152|256:152|284:176|284:176|308:199|308:199|328:199,2,183.333327869574,2|0|0 +46,322,1308494,2,0,B|68:284|182:252|208:368|160:384|164:318|240:304|256:251|256:251|272:304|352:320|352:384|304:368|333:281|364:284|455:262|475:337|475:337,2,549.999983608723,2|2|4 +256,384,1310767,5,8 +332,219,1311070,1,0 +332,219,1311222,1,2 +179,217,1311525,1,0 +230,40,1311828,2,0,B|216:49|207:74|216:97|232:112|256:118|280:112|296:97|304:74|296:49|282:40,1,183.333327869574,2|0 +448,74,1312434,6,0,B|489:156,1,91.6666639347872,2|8 +444,238,1312737,2,0,B|416:326,1,91.6666639347872,0|8 +324,366,1313040,2,0,B|310:324|256:313|256:313|206:321|186:370,1,183.333327869574,8|0 +95,325,1313494,2,0,B|68:238,1,91.6666639347872,8|0 +23,156,1313797,2,0,B|64:74,1,91.6666639347872,8|4 +336,47,1314403,6,0,B|296:135|336:224,1,183.333327869574,8|0 +376,304,1314858,2,0,B|296:308|256:361|256:361|211:308|135:304,1,274.999991804362,2|8 +43,288,1315464,2,0,B|51:240|83:208,1,91.6666639347872,2|8 +174,224,1315767,6,0,B|215:135|175:47,1,183.333327869574,4|0 +83,51,1316222,2,0,B|139:107|127:200|43:240|-24:204,1,274.999991804362,8|2 +68,360,1316979,6,0,B|144:308,1,91.6666639347872,0|8 +256,372,1317282,2,0,B|256:280,1,91.6666639347872,8|0 +256,188,1317585,1,4 +444,360,1317888,6,0,B|368:308,1,91.6666639347872,0|8 +209,116,1318343,2,0,B|254:100|297:116,1,91.6666639347872 +344,43,1318646,2,0,B|256:2|167:43,1,183.333327869574,0|8 +44,176,1319252,1,0 +116,120,1319403,2,0,B|180:176|168:288,1,183.333327869574,2|0 +256,316,1319858,2,0,B|256:135,1,183.333327869574,8|0 +396,120,1320312,2,0,B|332:176|344:288,1,183.333327869574,2|0 +428,324,1320767,6,0,B|420:280|424:232,1,91.6666639347872,8|8 +464,84,1321070,2,0,B|472:128|468:176,1,91.6666639347872,0|4 +320,336,1321525,6,0,B|324:292|308:244,1,91.6666639347872,0|8 +256,172,1321828,1,0 +210,102,1321979,2,0,B|255:86|298:102,1,91.6666639347872 +191,339,1322434,6,0,B|187:295|202:249,1,91.6666639347872,0|8 +256,172,1322737,1,0 +312,100,1322888,1,0 +348,14,1323040,2,0,B|401:10|401:10|457:6|485:38|489:83,1,183.333327869574,2|0 +404,123,1323494,2,0,B|320:171|304:268,1,183.333327869574,8|0 +207,268,1323949,2,0,B|191:171|107:123,1,183.333327869574,2|0 +107,123,1324403,1,8 +192,92,1324555,5,0 +256,28,1324706,1,0 +320,92,1324858,1,4 +256,192,1325009,12,4,1326676 +60,192,1327131,5,0 +60,192,1327282,1,0 +160,136,1327434,1,0 +160,124,1327509,1,0 +160,112,1327585,5,2 +256,28,1327737,1,0 +352,112,1327888,1,0 +452,192,1328040,1,8 +352,272,1328191,1,0 +256,356,1328343,1,0 +160,272,1328494,1,4 +40,166,1328797,6,0,B|40:120|64:80,1,91.3333338232835,0|8 +255,146,1329252,2,0,B|268:176|244:208|256:236,1,91.3333338232835,0|2 +471,166,1329706,2,0,B|472:120|448:80,1,91.3333338232835,0|8 +364,312,1330161,5,0 +364,312,1330312,2,0,B|336:380|256:408|175:380|147:312,1,274.000001469851,4|8 +192,232,1330919,1,0 +256,168,1331070,1,0 +320,232,1331222,2,0,B|364:232|408:208|424:163|416:107,1,182.666667646567,2|0 +340,64,1331676,2,0,B|296:96|256:64|256:64|216:32|172:64,1,182.666667646567,8|2 +96,107,1332131,6,0,B|88:164|104:208|148:232|192:232,1,182.666667646567,4|0 +284,232,1332585,2,0,B|332:92|470:44,1,274.000001469851,8|2 +227,151,1333494,2,0,B|178:290|40:338,1,274.000001469851,8|4 +40,156,1334252,6,0,B|84:168|84:168|132:168,1,91.3333338232835,0|8 +179,91,1334555,2,0,B|203:55|256:27|308:55|332:91,1,182.666667646567,0|2 +360,180,1335009,1,0 +336,268,1335161,1,0 +256,308,1335312,1,8 +176,268,1335464,1,8 +152,180,1335616,1,0 +148,88,1335767,6,0,B|208:56|208:56|256:84|316:88,1,182.666667646567,4|0 +408,84,1336222,2,0,B|376:144|376:144|404:192|408:252,1,182.666667646567,8|0 +364,344,1336676,2,0,B|304:312|304:312|256:340|196:344,1,182.666667646567,2|0 +104,300,1337131,2,0,B|136:240|136:240|108:192|104:132,1,182.666667646567,8|0 +27,183,1337585,6,0,B|75:244|175:236|215:147|179:83,1,274.000001469851,4|8 +256,32,1338191,2,0,B|256:216,1,182.666667646567,0|2 +332,84,1338797,1,0 +332,83,1338949,2,0,B|296:148|336:236|436:244|484:183,1,274.000001469851,8|4 +256,192,1339555,12,6,1342131 +112,112,1342282,5,0 +168,40,1342434,1,0 +256,12,1342585,1,8 +344,40,1342737,1,0 +400,112,1342888,1,0 +332,170,1343040,6,0,B|312:129|257:106|202:128|183:172,1,182.666667646567,6|0 +181,260,1343494,2,0,B|201:301|256:324|311:302|330:258,1,182.666667646567,10|0 +256,216,1343949,1,0 +208,384,1344252,2,0,B|304:384|304:384,1,91.3333338232835,0|8 +256,216,1344706,1,0 +124,38,1345161,2,0,B|138:78|197:93|226:63|226:19|190:5|160:49|208:93|256:93|256:93|303:93|351:49|321:5|285:19|285:63|318:93|387:71|395:34,1,421.333338858287,2|0 +440,216,1346070,5,2 +388,304,1346222,1,10 +256,140,1346525,1,0 +204,212,1346676,2,0,B|212:264|256:280|256:280|308:300|304:356,1,182.666667646567 +208,348,1347131,2,0,B|204:300|256:280|256:280|300:264|312:208,1,182.666667646567,8|0 +256,140,1347585,1,0 +72,216,1347888,1,2 +124,304,1348040,1,10 +160,156,1348343,6,0,B|213:103|186:3|123:-2|81:55|123:108|202:81|257:14|257:14|310:81|390:108|432:55|390:-2|326:3|299:103|352:156,1,639.333336762985,0|2 +344,236,1349555,6,0,B|388:316,1,91.3333338232835,0|0 +256,364,1349858,2,0,B|256:252,1,91.3333338232835,8|0 +124,315,1350161,2,0,B|168:236,1,91.3333338232835,0|4 +44,44,1350767,1,8 +335,282,1351222,2,0,B|376:257|405:184|376:114|328:68|258:49|185:68|135:114|111:184|135:257|178:284,1,548.000002939701,2|2 +468,44,1352585,1,8 +357,277,1353040,6,0,B|339:348|254:387|171:346|154:277,1,274.000001469851,4|8 +22,192,1353797,2,0,B|5:-6|238:-24|305:41|372:108|388:158|354:192|256:441|156:192|122:158|138:108|205:41|272:-24|505:-6|483:194,1,1096.0000058794,0|0 +377,321,1355767,2,0,B|319:381|256:336|256:336|187:384|131:316,1,274.000001469851,2|2 +200,268,1356373,1,0 +140,212,1356525,1,0 +132,132,1356676,1,4 +180,64,1356828,1,0 +256,40,1356979,1,0 +332,64,1357131,1,6 +380,132,1357282,1,0 +372,212,1357434,1,0 +312,268,1357585,1,4 +52,80,1358494,6,0,B|52:37|113:29|132:69|132:69|150:110|197:110|214:69|214:69|229:29|279:29|296:69|296:69|312:110|360:110|376:69|376:69|397:29|458:37|458:81,2,548.000002939701,8|0|8 +256,192,1360767,1,0 +52,302,1361222,2,0,B|52:345|113:353|132:313|132:313|150:272|197:272|214:313|214:313|229:353|279:353|296:313|296:313|312:272|360:272|376:313|376:313|397:353|458:345|458:301,2,548.000002939701,0|8|0 +256,44,1363494,5,0 +244,104,1363949,1,8 +264,152,1364403,1,0 +244,208,1364858,1,4 +256,356,1365767,1,8 +256,356,1366373,5,0 +256,356,1366525,1,0 +256,356,1366676,1,0 +308,196,1367131,1,0 +176,288,1367585,1,8 +336,288,1368040,1,0 +204,196,1368494,1,0 +361,51,1369403,6,0,B|334:77|294:90|255:51|255:24|255:24|255:51|215:90|175:77|149:51,1,274.000001469851,8|0 +140,60,1370009,1,0 +127,69,1370161,1,0 +117,79,1370312,1,0 +256,192,1370767,1,0 +344,296,1371222,1,8 +320,324,1371373,1,0 +292,340,1371525,1,0 +260,344,1371676,1,8 +220,340,1371828,1,0 +192,324,1371979,1,0 +168,296,1372131,1,4 +256,192,1383040,12,0,1386676 +256,48,1387131,5,4 +256,216,1387585,1,0 +256,216,1388040,1,8 +176,236,1388191,1,0 +96,256,1388343,1,0 +40,200,1388494,6,0,B|20:156|40:112,1,91.3333338232835 +164,88,1388797,2,0,B|144:128|144:128|108:156,1,91.3333338232835,0|4 +210,327,1389403,6,0,B|256:344|300:328,1,91.3333338232835,0|0 +208,192,1389706,2,0,B|254:175|298:191,1,91.3333338232835,0|4 +472,200,1390161,5,0 +472,200,1390312,2,0,B|492:156|472:112,1,91.3333338232835 +348,88,1390616,2,0,B|368:128|368:128|404:156,1,91.3333338232835,0|4 +256,216,1391222,5,0 +256,216,1391676,1,8 +336,236,1391828,1,0 +416,256,1391979,1,0 +384,332,1392131,6,0,B|296:308,1,91.3333338232835 +128,332,1392434,2,0,B|216:308,1,91.3333338232835,0|4 +384,128,1393040,6,0,B|296:152,1,91.3333338232835 +128,128,1393343,2,0,B|216:152,1,91.3333338232835,0|4 +408,232,1393797,5,0 +408,232,1393949,2,0,B|316:232,1,91.3333338232835 +104,232,1394252,2,0,B|195:232,1,91.3333338232835,0|4 +195,232,1394858,1,0 +302,40,1395312,6,0,B|210:40,1,91.3333338232835,8|0 +296,116,1395616,2,0,B|380:152,1,91.3333338232835 +300,220,1395919,2,0,B|256:228|256:228|212:220,1,91.3333338232835 +216,116,1396222,2,0,B|48:188,1,182.666667646567,4|0 +80,312,1396676,5,8 +164,260,1396828,1,0 +256,300,1396979,1,0 +256,300,1397131,1,4 +395,232,1397282,6,0,B|353:313,1,91.3333338232835,0|0 +256,300,1397585,1,8 +159,313,1397737,2,0,B|117:232,1,91.3333338232835,0|0 +256,116,1398040,1,4 +256,116,1398494,1,0 +356,336,1398949,6,0,B|356:244,1,91.3333338232835,8|0 +256,348,1399252,2,0,B|256:256,1,91.3333338232835 +156,336,1399555,2,0,B|156:244,1,91.3333338232835 +256,116,1399858,1,4 +280,32,1400009,2,0,B|208:32,2,45.6666669116418 +356,84,1400312,1,8 +376,180,1400464,1,0 +348,276,1400616,1,0 +256,316,1400767,1,8 +164,276,1400919,1,0 +136,180,1401070,1,0 +152,88,1401222,6,0,B|112:24,2,45.6666669116418 +208,152,1401525,1,0 +256,72,1401676,2,0,B|256:24,2,45.6666669116418,8|0|0 +304,148,1401979,1,0 +360,88,1402131,2,0,B|400:24,2,45.6666669116418 +304,148,1402434,1,0 +256,216,1402585,2,0,B|256:308,1,91.3333338232835,8|0 +256,384,1402888,1,0 +176,352,1403040,5,2 +156,316,1403116,1,0 +136,280,1403191,1,0 +176,208,1403343,1,0 +184,128,1403494,2,0,B|204:84|256:60|308:84|328:128,1,182.666667646567,8|0 +336,208,1403949,1,8 +376,280,1404100,1,2 +356,316,1404176,1,0 +336,352,1404252,1,0 +256,384,1404403,5,8 +196,268,1404555,1,2 +316,268,1404706,1,0 +416,368,1404858,6,0,B|476:344|492:280|492:280|508:216,1,182.666667646567,4|0 +500,136,1405312,2,0,B|404:172|328:104,1,182.666667646567,8|0 +344,23,1405767,2,0,B|256:-16|167:23,1,182.666667646567,4|2 +11,135,1406373,6,0,B|107:171|180:105,1,182.666667646567,0|2 +330,277,1406979,2,0,B|404:212|500:248,1,182.666667646567,8|0 +180,277,1407585,2,0,B|107:212|11:247,1,182.666667646567,10|2 +120,332,1408040,5,8 +256,248,1408191,1,8 +392,332,1408343,1,0 +384,147,1408494,6,0,B|360:95|304:55|239:63|183:135|195:192|256:232|316:192|328:135|272:63|207:55|151:95|127:147,1,464.000007743836,4|2 +80,208,1409555,2,0,B|104:244|96:292,1,76.1333349488259 +12,180,1409858,2,0,B|12:136|40:100,1,76.1333349488259,8|0 +473,282,1410312,6,0,B|500:248|500:204,1,76.1333349488259,2|0 +415,95,1410616,2,0,B|408:140|432:176,1,76.1333349488259,0|8 +364,268,1410919,2,0,B|280:252,1,76.1333349488259,2|0 +148,116,1411222,2,0,B|232:132,1,76.1333349488259,2|0 +144,304,1411525,6,0,B|116:272|112:228,1,76.1333349488259,0|8 +368,80,1411828,2,0,B|396:112|400:156,1,76.1333349488259,2|8 +256,192,1412131,5,4 +256,192,1412282,12,8,1413494 +84,108,1413949,6,0,B|20:160,1,76.1333349488259,4|0 +84,251,1414252,2,0,B|128:180,1,76.1333349488259,8|2 +216,125,1414555,1,2 +216,125,1414631,1,0 +216,125,1414706,2,0,B|200:209,1,76.1333349488259,0|8 +312,208,1415009,2,0,B|296:125,1,76.1333349488259,0|2 +384,180,1415312,2,0,B|428:252,1,76.1333349488259,2|0 +493,160,1415616,2,0,B|428:108,1,76.1333349488259,0|4 +336,272,1416222,6,0,B|352:336|288:368|256:368|223:368|159:336|175:272,2,274.000001469851,2|2|8 +256,80,1417434,1,0 +193,107,1417585,2,0,B|181:86|174:41|209:-13|268:-15|316:8|345:44|324:108|313:114,1,274.000001469851,2|8 +496,256,1418494,1,2 +256,352,1418797,5,0 +216,228,1418949,1,8 +320,304,1419100,1,8 +192,304,1419252,1,8 +296,228,1419403,1,4 +16,256,1419858,5,2 +175,95,1420312,2,0,B|159:31|223:0|256:0|288:0|352:31|336:95,1,274.000001469851,2|8 +438,268,1421070,1,0 +361,333,1421222,2,0,B|334:307|294:294|255:333|255:360|255:360|255:333|215:294|175:307|149:333,1,274.000001469851,2|8 +80,288,1421828,2,0,B|108:248|160:236,1,91.3333338232835 +112,168,1422131,2,0,B|160:160|188:120,1,91.3333338232835,8|0 +256,76,1422434,1,0 +324,120,1422585,2,0,B|352:160|400:168,1,91.3333338232835,8|0 +357,237,1422888,2,0,B|400:248|432:288,1,91.3333338232835,0|4 +150,100,1423494,6,0,B|174:31|259:0|337:28|361:108,1,274.000001469851,2|2 +64,336,1424403,1,8 +256,212,1424706,1,0 +309,281,1424858,2,0,B|328:332|328:332|292:332|256:368|256:368|220:332|184:332|184:332|204:279,1,274.000001469851,2|8 +448,337,1425767,1,2 +180,88,1426222,6,0,B|203:42|256:31|310:43|333:90,1,182.666667646567,8|0 +362,187,1426676,2,0,B|324:295|164:293|148:177|148:177,3,274.000001469851,4|8|2|8 +40,360,1428494,1,2 +127,312,1428646,2,0,B|166:350|217:355,1,91.3333338232835,2|0 +305,353,1428949,2,0,B|346:350|385:312,1,91.3333338232835,8|8 +476,354,1429252,1,8 +488,248,1429403,5,8 +408,208,1429555,1,0 +320,144,1429706,1,0 +256,64,1429858,1,8 +192,144,1430009,1,0 +112,208,1430161,1,0 +24,248,1430312,1,4 +337,289,1430767,6,0,B|321:338|256:371|190:338|174:289,1,210.666669429143,2|0 +256,248,1431222,1,2 +256,56,1431525,1,0 +175,87,1431676,2,0,B|191:127|256:151|256:151|313:129|336:87,1,210.666669429143,8|0 +416,144,1432131,1,2 +416,336,1432434,2,0,B|368:312|368:312|344:264,1,105.333334714572,0|8 +166,266,1432888,2,0,B|144:312|144:312|96:336,1,105.333334714572,0|2 +96,144,1433343,5,0 +174,91,1433494,2,0,B|229:91|256:35|256:35|282:91|337:91,1,210.666669429143,8|0 +400,184,1433949,2,0,B|430:234|430:234|457:284|446:343|379:351|379:351,1,210.666669429143,2|0 +294,306,1434403,2,0,B|299:294|311:265|305:238|281:222|254:214|225:221|205:241|201:269|207:289|220:306,1,210.666669429143,8|0 +132,351,1434858,2,0,B|65:343|54:284|81:234|81:234|112:184,1,210.666669429143,2|0 +136,96,1435312,5,8 +215,45,1435464,2,0,B|222:20|255:3|288:20|296:45,1,105.333334714572,2|0 +376,96,1435767,1,2 +440,276,1436070,2,0,B|412:308|412:308|376:312|376:312|360:336,1,105.333334714572,0|8 +308,256,1436373,2,0,B|320:220|320:220|352:208|352:208|364:172,1,105.333334714572,2|0 +256,160,1436676,2,0,B|264:136|264:136|240:96|240:96|256:68|256:68,2,105.333334714572,8|0|0 +149,176,1437131,2,0,B|160:208|160:208|192:220|192:220|204:256,1,105.333334714572,8|0 +150,333,1437434,2,0,B|136:312|136:312|100:308|100:308|72:276,1,105.333334714572,0|4 +256,124,1437888,5,0 +176,92,1438040,2,0,B|192:43|257:10|323:43|339:92,1,210.666669429143,8|0 +360,192,1438494,2,0,B|136:192,1,210.666669429143,2|0 +176,292,1438949,2,0,B|192:341|257:374|323:341|339:292,1,210.666669429143,8|0 +416,348,1439403,5,2 +256,252,1439706,1,0 +256,252,1439858,1,8 +96,348,1440161,1,0 +96,348,1440312,1,2 +112,160,1440616,1,0 +164,80,1440767,1,8 +256,52,1440919,1,0 +348,80,1441070,1,0 +392,156,1441222,1,6 +256,192,1441373,12,2,1443040 +256,344,1443191,5,0 +344,312,1443343,1,10 +404,240,1443494,1,0 +404,144,1443646,1,8 +344,72,1443797,1,0 +256,40,1443949,1,8 +168,72,1444100,1,0 +108,144,1444252,1,8 +108,240,1444403,1,8 +168,312,1444555,5,8 +256,344,1444706,1,8 +344,312,1444858,1,4 +412,136,1445161,1,2 +452,52,1445312,1,8 +256,84,1445616,1,2 +256,176,1445767,1,0 +100,136,1446070,1,2 +60,52,1446222,1,8 +134,257,1446525,22,0,B|160:337|259:373|350:340|378:247,1,316.000004143715,2|2 +432,331,1447131,2,0,B|504:216|466:84|466:84,1,210.666669429143,8|0 +402,67,1447585,1,2 +256,188,1447888,1,0 +350,175,1448040,2,0,B|363:214|334:293|255:300|213:278,1,210.666669429143,8|0 +161,208,1448494,2,0,B|148:169|177:90|256:83|298:105,1,210.666669429143,2|0 +256,188,1448949,1,8 +112,68,1449252,1,0 +35,130,1449403,2,0,B|28:248|84:336,1,210.666669429143,2|0 +172,356,1449858,1,8 +300,236,1450161,38,0,B|323:218|338:177|323:136|295:111|254:100|213:111|185:136|170:177|185:218|209:236,1,316.000004143715,2|0 +104,228,1450767,1,8 +108,132,1450919,1,8 +164,56,1451070,1,0 +256,28,1451222,1,8 +348,56,1451373,1,8 +404,132,1451525,1,0 +408,228,1451676,1,8 +344,300,1451828,1,0 +256,332,1451979,1,8 +168,300,1452131,1,4 +44,44,1452585,21,2 +256,232,1453040,1,2 +312,60,1453343,1,0 +168,160,1453646,1,2 +348,160,1453949,1,2 +204,60,1454252,1,2 +256,136,1454403,1,8 +468,44,1454858,1,2 +448,232,1455161,5,0 +448,232,1455312,1,8 +296,352,1455616,1,0 +218,297,1455767,2,0,B|189:364|93:358|82:299|67:262|90:207|157:188|201:214|200:251,1,316.000004143715,2|8 +294,87,1456676,2,0,B|323:20|418:25|429:84|444:121|422:177|354:195|311:170|311:132,1,316.000004143715,2|8 +436,286,1457434,21,2 +350,317,1457585,1,8 +256,302,1457737,1,2 +185,247,1457888,1,2 +161,160,1458040,1,8 +177,66,1458191,1,0 +256,26,1458343,1,2 +334,66,1458494,1,10 +350,160,1458646,1,8 +326,247,1458797,1,0 +256,302,1458949,1,8 +161,317,1459100,1,8 +75,286,1459252,1,0 +40,200,1459403,38,0,B|8:160|16:88|64:48|136:56,1,210.666669429143,4|2 +200,112,1459858,1,8 +256,184,1460009,1,0 +312,112,1460161,1,2 +388,55,1460312,2,0,B|448:48|496:88|504:160|472:200,1,210.666669429143,0|0 +392,248,1460767,1,8 +296,288,1460919,2,0,B|289:263|256:246|223:263|215:288,1,105.333334714572,2|0 +128,240,1461222,1,0 +256,360,1461525,21,0 +256,360,1461676,1,8 +216,176,1461979,2,0,B|223:201|256:218|289:201|297:176,1,105.333334714572,2|0 +120,112,1462434,1,0 +168,32,1462585,1,8 +256,8,1462737,1,0 +344,32,1462888,1,0 +392,112,1463040,1,4 +256,192,1463191,12,2,1464858 +278,319,1465312,6,0,B|239:286|239:286|175:289|175:289,1,105.333334714572,8|0 +360,268,1465616,2,0,B|348:219|348:219|378:162|378:162,1,105.333334714572,2|8 +233,64,1465919,2,0,B|272:97|272:97|336:94|336:94,1,105.333334714572,0|8 +151,115,1466222,2,0,B|163:164|163:164|133:221|133:221,1,105.333334714572,8|0 +94,295,1466525,5,8 +194,352,1466676,2,0,B|203:291|254:272|254:272|315:249|310:184,1,210.666669429143,4|0 +328,96,1467131,1,8 +256,40,1467282,1,0 +184,96,1467434,1,0 +201,192,1467585,2,0,B|196:249|257:272|257:272|308:291|318:352,1,210.666669429143,2|0 +418,295,1468040,5,8 +472,216,1468191,2,0,B|416:192|424:128|424:128,1,105.333334714572 +344,80,1468494,2,0,B|304:96|264:80|256:48|256:48|248:80|208:96|168:80,1,210.666669429143,2|0 +168,176,1468949,2,0,B|216:208|216:208|256:184|256:184|296:208|296:208|344:176,1,210.666669429143,8|0 +256,280,1469403,5,2 +40,216,1469706,2,0,B|97:187|88:128|88:128,1,105.333334714572,0|8 +159,47,1470009,1,0 +257,24,1470161,1,0 +355,49,1470312,2,0,B|483:105|483:330|316:388|254:334|254:334|168:248|196:161|312:161|339:248|254:334|254:334|168:392|24:307|24:105|173:40,1,1264.00001657486,2|8 +264,76,1472282,5,8 +264,76,1472434,1,0 +264,76,1472585,1,8 +264,76,1472737,1,0 +264,76,1472888,1,8 +264,76,1473040,1,4 +400,320,1473494,1,6 +112,320,1473949,1,4 +256,192,1474100,12,0,1476676 +256,44,1505977,5,4 +172,96,1506172,1,4 +256,148,1506367,2,2,B|256:364,1,200,4|6 +100,332,1507536,6,0,B|80:216|132:128,1,200,0|8 +300,36,1508315,2,0,B|372:104|488:120,1,200 +472,220,1508899,1,0 +376,188,1509094,1,0 +216,308,1509484,2,0,B|112:308|16:376,1,200,8|0 +68,172,1510263,5,4 +220,44,1510652,1,0 +228,144,1510847,1,0 +308,204,1511042,1,8 +428,44,1511432,1,0 +488,236,1511821,1,0 +440,324,1512016,1,0 +348,284,1512211,1,0 +160,356,1512600,2,0,B|192:256|164:156,1,200,8|0 +348,76,1513380,5,6 +152,44,1513769,2,0,B|108:60|96:128,2,100,0|0|8 +256,216,1514549,1,0 +256,216,1514938,2,0,B|368:216,2,100 +64,164,1515717,2,0,B|124:72|224:52,1,200,8|0 +420,84,1516497,5,4 +384,280,1516886,1,0 +192,332,1517276,1,8 +68,176,1517665,1,0 +268,200,1518055,2,0,B|320:224|364:192,2,100 +116,104,1518834,5,8 +120,204,1519029,1,0 +220,188,1519224,1,0 +216,88,1519419,2,2,B|316:60|428:92,1,200 +364,176,1520003,1,0 +428,252,1520198,1,2 +260,364,1520587,5,0 +252,264,1520782,1,0 +156,288,1520977,2,2,B|116:188|180:88,1,200 +256,36,1521562,1,0 +396,176,1521951,5,8 +304,132,1522146,1,0 +204,124,1522341,1,0 +120,180,1522536,2,2,B|100:296|212:356,1,200 +256,268,1523120,1,0 +316,348,1523315,2,2,B|415:295|395:179,1,200 +296,168,1523899,1,0 +268,72,1524094,2,2,B|180:120|56:68,1,200 +32,164,1524678,1,0 +172,304,1525068,1,8 +216,216,1525263,5,0 +316,224,1525458,1,0 +332,124,1525652,2,2,B|460:112|492:216,1,200 +420,280,1526237,1,0 +336,336,1526432,2,2,B|240:352|128:336,1,200 +56,280,1527016,1,0 +68,180,1527211,2,2,B|172:116|204:20,1,200 +256,116,1527795,1,0 +432,212,1528185,1,8 +336,244,1528380,5,0 +408,316,1528574,1,0 +316,356,1528769,2,2,B|112:356,1,200 +16,356,1529354,1,0 +32,156,1529743,6,0,B|16:108|32:52,1,100,8|0 +104,128,1530133,2,0,B|120:180|176:208,1,100 +200,108,1530523,2,0,B|222:195|310:225|393:202|416:96,1,300,4|0 +476,184,1531302,1,8 +452,320,1531497,5,0 +360,360,1531691,1,0 +276,308,1531886,2,2,B|180:348|80:256,1,200 +32,184,1532471,1,0 +32,184,1532665,2,2,B|60:52|176:52,1,200 +260,64,1533250,1,0 +260,64,1533445,2,2,B|344:148|260:248,1,200 +196,316,1534029,2,0,B|80:316,2,100,0|0|8 +336,316,1534613,5,0 +432,296,1534808,1,0 +436,196,1535003,2,2,B|428:84|300:32,1,200 +256,116,1535587,1,0 +188,44,1535782,2,2,B|83:85|75:197,1,200 +176,184,1536367,2,0,B|184:240|135:282,1,100,0|2 +200,356,1536756,1,4 +256,164,1537146,1,0 +320,352,1537536,1,8 +448,292,1537730,6,0,B|412:244|444:184,2,100,0|0|2 +484,96,1538510,1,0 +392,64,1538704,1,0 +312,124,1538899,1,2 +216,300,1539289,1,0 +124,340,1539484,1,0 +108,240,1539678,1,2 +188,180,1539873,2,0,B|156:80|44:40,2,200,0|0|8 +280,72,1540847,5,2 +380,84,1541042,1,0 +424,172,1541237,1,2 +440,272,1541432,1,0 +372,344,1541626,2,2,B|264:384|188:292,1,200,2|0 +256,220,1542211,6,0,B|144:220,1,100,8|0 +168,120,1542600,2,0,B|56:120,1,100 +40,216,1542990,2,0,B|104:304|216:320,1,200,4|0 +256,232,1543574,1,0 +256,232,1543769,1,8 +388,272,1543964,5,0 +404,172,1544159,1,0 +364,80,1544354,2,2,B|160:80,1,200 +72,124,1544938,1,0 +160,168,1545133,2,2,B|108:268|192:360,1,200 +260,288,1545717,1,0 +336,352,1545912,1,2 +388,160,1546302,1,0 +484,184,1546497,1,0 +464,88,1546691,2,2,B|364:48|248:88,1,200 +256,180,1547276,1,0 +256,192,1547471,12,6,1549224 +408,32,1550782,5,6 +104,132,1551172,1,0 +408,228,1551562,1,8 +100,312,1551951,1,2 +256,272,1552341,5,0 +256,172,1552536,1,0 +256,272,1552730,1,0 +256,72,1553120,1,8 +56,72,1553510,6,2,B|84:164|20:264,1,200,2|6 +160,120,1554289,2,2,B|256:80|356:124,1,200,2|10 +492,268,1555068,2,2,B|429:169|457:77,1,200 +356,80,1555652,1,0 +260,60,1555847,1,2 +256,220,1556237,1,8 +256,220,1556626,1,0 +224,316,1556821,1,0 +124,308,1557016,5,6 +256,60,1557406,1,0 +380,312,1557795,1,8 +256,60,1558185,2,2,B|256:264,1,200,2|0 +256,260,1558769,1,0 +256,260,1558964,1,0 +256,192,1559354,12,0,1561302 +68,116,1561691,5,0 +168,116,1561886,1,8 +216,136,1561984,1,8 +260,116,1562081,1,8 +360,116,1562276,1,8 +324,208,1562471,1,8 +424,192,1562665,1,0 +360,116,1562860,1,0 +432,280,1563055,6,0,B|384:300|320:292,1,100,0|6 +184,160,1563639,2,0,B|128:160|104:108,2,100,0|0|8 +368,80,1564419,2,2,B|360:196|276:256,1,200 +228,168,1565003,1,0 +280,252,1565198,1,0 +100,336,1565587,6,2,B|32:272|32:136,1,200,10|2 +84,64,1566172,2,0,B|140:64|180:96,1,100,0|4 +256,276,1566756,1,2 +368,108,1567146,1,8 +488,268,1567536,2,2,B|440:348|304:328,1,200 +212,348,1568120,1,0 +200,248,1568315,2,2,B|136:164|12:176,1,200,2|10 +168,40,1569094,1,0 +256,84,1569289,2,0,B|276:28,2,50,0|0|4 +472,192,1569873,5,0 +256,336,1570263,1,8 +40,192,1570652,2,2,B|248:192,1,200,2|0 +240,192,1571237,1,0 +240,192,1571432,1,0 +256,192,1571821,12,0,1573769 +392,292,1574159,5,0 +360,156,1574354,1,8 +236,92,1574549,1,0 +128,180,1574743,1,0 +112,320,1574938,1,8 +240,264,1575133,1,0 +128,180,1575328,1,0 +284,152,1575523,5,0 +300,200,1575620,1,0 +316,248,1575717,1,4 +68,48,1585068,5,2 +132,280,1585652,1,2 +192,48,1586237,1,2 +432,192,1586626,5,2 +192,192,1587211,1,2 +316,88,1587600,5,0 +416,72,1587795,1,0 +316,88,1587990,2,2,B|344:196|288:288,1,200,2|2 +220,352,1588574,1,0 +200,256,1588769,2,2,B|124:192|12:168,1,200 +64,80,1589354,1,0 +64,80,1589549,1,2 +260,44,1589938,2,2,B|412:44,1,150,0|8 +488,104,1590717,5,0 +472,204,1590912,1,0 +376,236,1591107,1,2 +204,340,1591497,1,0 +248,252,1591691,1,0 +148,260,1591886,1,2 +28,100,1592276,1,0 +128,104,1592471,1,0 +208,44,1592665,2,2,B|324:32|380:132,1,200 +428,216,1593250,1,0 +484,24,1593639,1,8 +452,120,1593834,5,0 +428,216,1594029,1,0 +340,164,1594224,1,2 +256,344,1594613,1,0 +256,244,1594808,1,0 +256,344,1595003,1,2 +60,300,1595393,1,0 +156,276,1595587,1,0 +132,180,1595782,2,2,B|108:72|20:8,1,200 +20,316,1596756,1,8 +160,328,1596951,5,0 +260,348,1597146,1,0 +328,276,1597341,1,2 +300,180,1597536,1,0 +204,164,1597730,1,2 +204,164,1598315,1,8 +28,260,1598704,5,0 +80,172,1598899,1,0 +156,240,1599094,1,0 +320,168,1599484,1,0 +280,76,1599678,1,0 +380,84,1599873,1,8 +404,180,1600068,6,0,B|428:284,2,100,0|8|10 +256,44,1600847,1,0 +256,144,1601042,1,0 +256,44,1601237,1,2 +56,68,1601626,2,0,B|88:120|56:168,1,100 +52,264,1602016,2,2,B|96:364|232:352,1,200 +312,328,1602600,1,0 +152,208,1602990,1,8 +208,80,1603185,5,2 +304,100,1603380,1,0 +400,80,1603574,1,2 +464,268,1603964,1,0 +404,348,1604159,1,0 +304,352,1604354,1,2 +128,256,1604743,1,0 +224,280,1604938,1,0 +316,236,1605133,2,2,B|440:236|500:136,2,200,2|0|2 +224,280,1606107,1,8 +256,144,1606302,5,0 +160,120,1606497,1,0 +96,196,1606691,1,2 +292,232,1607081,1,0 +348,316,1607276,1,0 +444,292,1607471,1,2 +248,328,1607860,1,0 +248,328,1608055,2,2,B|148:292|108:176,1,200,2|0 +116,88,1608639,1,2 +200,144,1608834,2,0,B|252:164|312:144,2,100,0|0|8 +252,60,1609419,6,0,B|300:40|356:64,1,100,2|0 +440,100,1609808,2,0,B|420:148|456:216,1,100,2|0 +408,288,1610198,1,2 +124,192,1610782,5,8 +252,344,1611172,1,0 +156,316,1611367,1,0 +60,348,1611562,1,0 +44,148,1611951,2,0,B|112:68|224:64,1,200,0|8 +356,36,1612536,5,0 +328,132,1612730,1,0 +428,140,1612925,2,2,B|448:256|344:324,1,200 +276,256,1613510,1,0 +204,324,1613704,2,2,B|100:324|64:188,1,200 +36,104,1614289,1,0 +116,44,1614484,2,2,B|324:44,1,200 +416,52,1615068,1,0 +480,128,1615263,2,2,B|472:232|352:284,1,200 +356,180,1615847,2,2,B|296:172|272:244,2,100 +132,352,1616626,5,8 +60,232,1616821,1,8 +120,104,1617016,1,8 +240,32,1617211,1,8 +376,72,1617406,1,8 +416,208,1617600,1,0 +236,204,1617795,6,2,B|310:134|424:135,1,200,2|0 +472,328,1618574,1,8 +388,276,1618769,1,0 +476,228,1618964,1,0 +472,328,1619159,1,0 +324,340,1619354,6,0,B|220:364|124:320,1,200,6|0 +44,140,1620133,1,8 +132,188,1620328,1,0 +204,116,1620523,2,2,B|312:116|396:36,1,200 +456,120,1621107,1,0 +364,156,1621302,1,2 +468,328,1621691,1,8 +284,248,1622081,2,2,B|212:188|212:188|192:64,1,200,2|6 +28,192,1622860,5,2 +184,316,1623250,1,8 +248,124,1623639,2,2,B|352:60|458:182|384:292,1,300,2|0 +389,283,1624419,1,2 +200,216,1624808,1,8 +44,340,1625198,1,0 +44,340,1625393,5,0 +44,340,1625490,1,0 +44,340,1625587,2,0,B|88:136,1,200,6|0 +276,80,1626367,2,0,B|372:52|476:88,2,200,8|2|0 +184,124,1627341,1,0 +256,192,1627536,1,0 +256,192,1627925,12,0,1629873 +256,36,1630263,5,0 +292,56,1630458,1,8 +312,88,1630652,1,0 +316,128,1630847,1,0 +308,168,1631042,1,8 +296,208,1631237,1,0 +272,240,1631432,1,0 +244,268,1631626,1,0 +204,276,1631821,6,2,B|91:252|43:156,1,200,6|2 +304,48,1632600,2,0,B|428:72|444:204,2,200,8|2|0 +304,48,1633574,1,0 +304,48,1633769,1,2 +456,284,1634159,2,0,B|372:344|264:304,2,200,8|2|4 +240,104,1635328,6,2,B|140:108|72:204,1,200,2|8 +340,108,1636107,2,2,B|304:212|172:196,1,200 +180,196,1636691,1,0 +180,196,1636886,2,2,B|196:324|328:324,1,200,2|10 +452,84,1637665,1,0 +452,84,1637860,5,8 +452,84,1637958,1,8 +452,84,1638055,2,0,B|348:116|244:68,1,200,6|0 +64,124,1638834,1,8 +64,124,1639224,2,2,B|64:228|152:308,1,200 +244,316,1639808,1,0 +256,216,1640003,1,0 +256,192,1640393,12,8,1641951 +416,192,1642536,5,0 +416,192,1642730,2,8,B|432:116,2,66.6666666666667,0|8|8 +352,272,1643120,2,0,B|368:356,2,66.6666666666667,8|8|0 +292,192,1643510,2,0,B|272:120,2,66.6666666666667,0|8|8 +232,272,1643899,2,0,B|204:348,2,66.6666666666667,8|0|0 +132,296,1644289,1,4 +104,96,1644678,5,0 +196,136,1644873,1,0 +288,96,1645068,1,8 +456,208,1645458,1,0 +356,192,1645652,1,0 +264,228,1645847,2,2,B|152:228|60:324,2,200,10|2|10 +432,116,1647016,1,6 +264,228,1647406,5,8 +344,172,1647600,2,0,B|364:124,2,50 +248,152,1647990,1,0 +176,80,1648185,1,8 +76,96,1648380,1,0 +32,188,1648574,1,0 +84,272,1648769,1,0 +132,260,1648867,1,0 +176,280,1648964,6,2,B|248:344|368:312,1,200,10|2 +248,152,1649743,1,8 +424,56,1650133,2,2,B|320:28|224:56,1,200,2|10 +256,252,1650912,2,2,B|368:252|432:364,1,200,2|10 +220,344,1651691,5,2 +120,328,1651886,1,0 +128,228,1652081,1,8 +36,48,1652471,2,2,B|244:48,1,200,2|10 +436,60,1653250,2,2,B|420:160|468:268,1,200,2|10 +376,304,1653834,5,0 +292,192,1654029,1,0 +268,236,1654126,1,0 +220,248,1654224,1,0 +172,236,1654321,1,0 +148,192,1654419,1,8 +84,68,1654613,1,0 +32,196,1654808,5,0 +32,196,1655003,1,0 +32,196,1655198,1,10 +256,348,1655587,1,2 +476,192,1655977,1,10 +256,40,1656367,2,0,B|256:152,2,100,2|0|6 +160,192,1657146,5,0 +256,220,1657341,1,0 +340,164,1657536,1,8 +156,92,1657925,1,0 +80,156,1658120,1,0 +92,256,1658315,1,10 +276,176,1658704,6,2,B|312:288|196:344,2,200,2|10|2 +212,100,1659678,1,0 +308,68,1659873,1,8 +472,184,1660263,2,0,B|472:244|424:280,2,100,0|0|8 +376,360,1661042,1,0 +324,272,1661237,1,0 +324,272,1661334,1,0 +324,272,1661432,6,0,B|216:248|128:328,1,200,8|0 +52,268,1662016,1,0 +36,168,1662211,2,0,B|84:68|196:64,1,200,2|2 +256,144,1662795,1,0 +256,192,1662990,12,0,1666107 +256,192,1677016,12,4,1678574 +256,180,1678964,5,0 +256,180,1679159,1,0 +256,180,1679354,1,2 +160,308,1679743,1,0 +100,252,1679938,1,0 +56,184,1680133,2,0,B|140:92,1,100,2|0 +196,76,1680523,2,0,B|308:76,3,100,0|0|2|0 +376,76,1681302,2,0,B|412:124|396:176,1,100 +364,244,1681691,5,4 +208,272,1682081,2,0,B|208:392,2,100,0|0|2 +68,192,1682860,1,0 +140,156,1683055,1,0 +152,76,1683250,1,2 +256,200,1683639,1,0 +364,80,1684029,1,2 +428,128,1684224,1,0 +440,208,1684419,1,0 +392,272,1684613,1,0 +392,272,1684808,6,0,B|304:264|180:304,1,200,4|0 +256,152,1685587,1,2 +256,152,1685782,1,0 +256,152,1685977,1,0 +100,188,1686367,1,2 +256,228,1686756,1,0 +412,188,1687146,1,2 +364,124,1687341,1,0 +292,92,1687536,1,0 +220,124,1687730,1,0 +172,188,1687925,1,2 +472,312,1688315,5,0 +400,348,1688510,1,0 +300,348,1688704,1,8 +184,312,1688899,1,8 +136,180,1689094,1,8 +248,68,1689289,1,8 +400,164,1689484,1,6 +56,68,1690263,5,0 +32,132,1690393,1,0 +36,200,1690523,1,8 +68,256,1690652,1,8 +124,296,1690782,1,8 +192,300,1690912,1,0 +252,272,1691042,6,2,B|360:300|428:200,1,200,6|2 +300,48,1691821,1,8 +156,188,1692211,1,2 +284,240,1692406,1,0 +396,172,1692600,2,0,B|452:160|492:200,2,100,0|0|2 +256,316,1693380,1,8 +220,120,1693769,6,2,B|128:48|32:120,1,200,2|6 +56,316,1694549,2,2,B|260:316,1,200,2|10 +452,280,1695328,1,2 +424,184,1695523,1,0 +444,88,1695717,1,0 +348,120,1695912,1,0 +248,128,1696107,1,2 +256,328,1696497,1,8 +92,212,1696886,1,0 +92,212,1697081,5,0 +92,212,1697178,1,0 +92,212,1697276,2,2,B|191:234|304:212,2,200,6|2|10 +200,44,1698445,2,2,B|292:20|416:52,1,200 +444,136,1699029,1,0 +408,232,1699224,1,0 +256,192,1699613,12,0,1701562 +428,192,1701951,5,4 +376,192,1702049,1,0 +328,192,1702146,1,0 +280,192,1702243,1,0 +228,192,1702341,6,0,B|240:260,2,66.6666666666667,0|0|0 +132,228,1702730,2,0,B|100:300,2,66.6666666666667,0|0|0 +100,132,1703120,2,0,B|28:152,2,66.6666666666667 +192,88,1703510,6,2,B|284:44|404:92,1,200,6|2 +420,284,1704289,2,2,B|332:360|228:284,1,200,10|2 +156,352,1704873,1,0 +156,252,1705068,1,0 +60,280,1705263,1,0 +52,180,1705458,2,2,B|148:132|192:32,1,200,2|10 +360,284,1706237,6,2,B|448:204|384:92,2,200,2|6|2 +256,112,1707406,1,8 +184,300,1707795,2,2,B|96:268|56:136,1,200 +32,56,1708380,1,0 +132,56,1708574,2,2,B|352:56,1,200,2|10 +480,188,1709354,5,8 +460,288,1709549,1,8 +412,280,1709646,1,8 +380,240,1709743,1,6 +200,328,1710133,2,0,B|160:236|196:120,2,200,0|8|2 +100,152,1711302,1,0 +28,220,1711497,1,0 +16,120,1711691,1,8 +96,60,1711886,1,0 +196,56,1712081,2,2,B|308:32|400:88,1,200 +356,176,1712665,1,0 +308,264,1712860,1,4 +256,192,1712958,12,0,1714419 +256,308,1748243,6,0,B|256:364,2,54.9999983608723 +256,252,1749012,2,0,B|244:216|268:192|256:144,1,109.999996721745,0|0 +284,36,1750038,1,0 +228,36,1750294,1,0 +347,99,1750807,6,0,B|392:128|424:192|392:260|348:284,1,219.999993443489 +256,192,1752602,1,0 +164,100,1753115,2,0,B|120:124|88:192|120:256|164:284,1,219.999993443489 +332,292,1755166,54,0,B|320:352|256:392|192:352|180:292,1,219.999993443489 +176,236,1756448,1,0 +228,216,1756704,1,0 +284,216,1756961,1,0 +336,236,1757217,1,0 +436,188,1757730,1,0 +348,124,1758243,5,0 +308,84,1758499,2,0,B|256:68|256:68|204:84,1,109.999996721745,0|0 +204,140,1759268,2,0,B|200:184|256:220|312:184|308:140,1,164.999995082617,0|0 +356,332,1760807,6,0,B|324:360|288:364|256:336|256:336|224:364|188:360|156:332,1,219.999993443489,0|0 +204,272,1762345,2,0,B|256:300|308:272,1,109.999996721745,2|0 +336,188,1763371,2,0,B|304:168|256:188|256:188|208:208|176:196,1,165.99999386549,4|8 +100,164,1764140,6,0,B|76:132|74:87,1,82.9999969327451,0|8 +140,32,1764653,2,0,B|220:56,1,82.9999969327451,0|8 +300,76,1765166,1,0 +324,156,1765422,2,0,B|304:188|256:220|208:188|186:154,1,165.99999386549,2|8 +212,76,1766192,1,0 +292,55,1766448,2,0,B|372:32,1,82.9999969327451,2|0 +436,86,1766961,2,0,B|436:132|412:164,1,82.9999969327451,8|0 +336,236,1767474,2,0,B|256:196|176:236,2,165.99999386549,4|8|2 +416,252,1768756,5,0 +432,172,1769012,1,8 +348,156,1769268,2,0,B|260:155|212:76,1,165.99999386549,2|0 +299,76,1770038,2,0,B|251:155|163:155,1,165.99999386549,2|8 +80,144,1770807,1,0 +132,80,1771063,1,2 +52,68,1771320,1,0 +16,196,1771576,2,0,B|36:360,1,165.99999386549,4|8 +116,340,1772345,2,0,B|108:280|108:280|100:220|144:188,1,165.99999386549,2|0 +224,168,1773115,5,8 +288,216,1773371,1,0 +367,196,1773627,2,0,B|412:164|404:104|404:104|396:44,2,165.99999386549,2|8|8 +320,128,1774909,1,0 +256,72,1775166,1,2 +196,128,1775422,1,0 +180,208,1775679,6,0,B|200:284|272:284|272:284|308:284,1,165.99999386549,4|8 +392,288,1776448,2,0,B|372:364|300:364|300:364|264:364,1,165.99999386549,2|0 +180,360,1777217,2,0,B|156:272|64:256,1,165.99999386549,2|8 +40,176,1777986,5,0 +96,116,1778243,1,8 +176,100,1778499,1,0 +256,112,1778756,1,2 +344,192,1779012,1,0 +256,272,1779268,1,8 +168,192,1779525,1,0 +256,112,1779781,1,4 +324,44,1780038,54,0,B|408:32,1,82.9999969327451,0|8 +500,96,1780551,2,0,B|416:108,1,82.9999969327451,0|2 +364,192,1781063,2,0,B|388:272,1,82.9999969327451,0|8 +336,347,1781576,2,0,B|256:314|170:348,1,165.99999386549,4|0 +148,192,1782345,2,0,B|124:272,1,82.9999969327451,2|0 +64,216,1782858,5,8 +88,136,1783115,2,0,B|112:56,1,82.9999969327451,2|0 +196,52,1783627,2,0,B|236:76|256:108|256:108|276:76|316:52,1,165.99999386549,4|0 +364,120,1784397,5,2 +256,176,1784653,1,0 +148,120,1784909,1,2 +336,224,1785166,1,0 +176,224,1785422,1,2 +256,360,1785679,1,0 +256,276,1785935,1,8 +256,192,1786192,1,0 +344,136,1786448,6,0,B|360:52,1,82.9999969327451,8|0 +256,1,1786961,2,0,B|256:84,1,82.9999969327451,2|0 +168,136,1787474,2,0,B|152:52,1,82.9999969327451,8|0 +44,176,1787986,2,0,B|24:340,1,165.99999386549,4|8 +112,296,1788756,2,0,B|116:212,1,82.9999969327451,0|2 +204,81,1789268,2,0,B|204:164,1,82.9999969327451,0|8 +280,244,1789781,5,0 +468,208,1790038,2,0,B|488:44,1,165.99999386549,4|8 +400,88,1790807,2,0,B|396:172,1,82.9999969327451,0|2 +309,302,1791320,2,0,B|308:220,1,82.9999969327451,0|8 +256,136,1791833,1,0 +184,56,1792089,2,0,B|208:24|256:8|300:24|330:57,1,165.99999386549,4|8 +328,156,1792858,5,0 +256,224,1793115,2,0,B|176:276|208:368,1,165.99999386549,2|8 +305,366,1793884,2,0,B|338:275|254:224,1,165.99999386549,2|8 +256,124,1794653,1,8 +256,48,1795166,5,0 +256,244,1795422,1,8 +184,124,1795679,1,8 +328,124,1795935,1,8 +400,244,1796192,1,4 +340,336,1796448,6,2,B|316:280|256:248|196:280|171:338,1,219.999993443489,2|0 +64,352,1797217,2,0,B|40:244,1,109.999996721745,2|0 +92,148,1797730,1,8 +184,84,1797986,2,0,B|268:100|316:156|332:228,1,219.999993443489,4|0 +256,308,1798756,1,8 +180,228,1799012,2,0,B|197:155|245:99|329:83,1,219.999993443489,2|0 +432,120,1799781,1,8 +456,336,1800038,6,0,B|348:288|348:160,1,219.999993443489,4|0 +312,56,1800807,1,8 +200,56,1801063,1,0 +256,216,1801320,1,2 +256,104,1801576,1,0 +164,160,1801833,1,8 +108,68,1802089,1,0 +165,160,1802345,2,0,B|164:288|56:336,1,219.999993443489,4|8 +12,236,1803115,5,0 +64,216,1803243,2,0,B|144:236|200:304,1,164.999995082617,2|0 +308,276,1803884,1,8 +232,196,1804140,1,2 +96,132,1804397,2,0,B|72:75|124:3|204:35|212:103,1,219.999993443489,4|8 +300,36,1805166,1,0 +356,36,1805294,2,0,B|388:32|408:16|408:16|424:40|448:52,1,109.999996721745,2|0 +500,72,1805679,5,2 +492,180,1805935,1,8 +388,148,1806192,1,0 +416,252,1806448,2,0,B|440:309|388:381|308:349|300:281,1,219.999993443489,4|8 +208,220,1807217,2,0,B|199:152|119:120|67:192|91:249,1,219.999993443489,2|0 +176,320,1807986,1,8 +280,352,1808243,1,0 +404,276,1808499,5,4 +404,276,1808756,2,0,B|336:192,1,109.999996721745,2|8 +108,107,1809268,2,0,B|178:193,1,109.999996721745,2|0 +328,100,1809781,2,0,B|360:48|408:28,2,109.999996721745,0|8|2 +185,283,1810551,2,0,B|152:336|104:356,2,109.999996721745,0|2|8 +296,228,1811320,6,0,B|260:212|252:172|216:156,2,109.999996721745,0|2|8 +368,144,1812089,5,8 +308,52,1812345,1,8 +256,36,1812474,1,0 +204,52,1812602,1,4 +144,144,1812858,1,2 +92,240,1813115,1,8 +148,336,1813371,1,0 +256,344,1813627,2,0,B|308:324|324:264,1,109.999996721745,2|0 +268,168,1814140,5,8 +204,256,1814397,2,0,B|292:332|408:316,1,219.999993443489,4|0 +484,236,1815166,1,8 +492,124,1815422,2,0,B|464:52|368:32|312:100,1,219.999993443489,2|0 +216,156,1816192,1,8 +56,48,1816448,6,0,B|8:168|92:252,1,219.999993443489,4|0 +200,256,1817217,2,0,B|312:256,1,109.999996721745,8|0 +420,240,1817730,2,0,B|360:332,1,109.999996721745,2|0 +312,360,1818115,1,2 +200,360,1818371,1,0 +152,332,1818499,2,0,B|92:240,1,109.999996721745,2|4 +256,252,1819012,5,0 +208,100,1819268,1,8 +332,196,1819525,1,0 +180,196,1819781,1,2 +304,100,1820038,5,0 +256,168,1820294,1,8 +400,108,1820551,1,0 +420,160,1820679,1,0 +440,212,1820807,1,4 +356,284,1821063,2,0,B|304:288|256:244|256:244|208:288|156:284,1,219.999993443489,2|0 +52,320,1821833,1,2 +72,212,1822089,5,0 +92,160,1822217,1,0 +112,108,1822345,1,8 +156,8,1822602,2,0,B|207:4|255:48|255:48|303:4|354:7,1,219.999993443489,4|0 +352,120,1823371,1,8 +256,172,1823627,1,2 +160,120,1823884,1,0 +192,224,1824140,5,8 +180,276,1824268,1,0 +204,324,1824397,1,8 +256,344,1824525,1,0 +308,324,1824653,1,2 +332,276,1824781,1,0 +320,224,1824909,1,4 +152,140,1830294,6,0,B|232:111,1,83.5000010451675,0|8 +360,244,1830807,2,0,B|280:272,1,83.5000010451675,0|2 +124,240,1831320,2,0,B|204:212,1,83.5000010451675,0|8 +388,144,1831833,2,0,B|308:172,1,83.5000010451675,0|2 +208,60,1832345,2,0,B|236:140,1,83.5000010451675,0|8 +304,324,1832858,2,0,B|276:244,1,83.5000010451675,0|4 +440,232,1833627,5,8 +428,152,1833884,1,0 +360,200,1834140,1,2 +504,180,1834397,5,0 +492,100,1834653,1,8 +416,72,1834909,1,0 +332,68,1835166,2,0,B|292:68|292:68|256:44|256:44|220:68|220:68|180:68,1,167.000002090335,2|8 +96,72,1835935,1,0 +20,100,1836192,1,2 +8,180,1836448,1,0 +84,148,1836704,1,8 +176,284,1837217,6,0,B|343:284,2,167.000002090335,0|8|0 +100,164,1838499,6,0,B|180:192,1,83.5000010451675,2|8 +412,164,1839012,2,0,B|332:192,1,83.5000010451675,0|2 +76,72,1839525,2,0,B|156:100,1,83.5000010451675,0|8 +436,72,1840038,2,0,B|356:100,1,83.5000010451675,0|2 +256,52,1840551,1,0 +256,136,1840807,1,8 +256,176,1840935,1,0 +256,216,1841063,1,8 +256,300,1841320,1,4 +92,312,1841833,6,0,B|48:304|28:264,1,83.5000010451675,8|0 +104,232,1842345,1,2 +168,180,1842602,1,0 +192,100,1842858,1,8 +196,16,1843115,1,0 +116,36,1843371,2,0,B|120:124|42:176,2,167.000002090335,4|8|2 +176,152,1844653,6,4,B|256:136|256:136|338:152,1,167.000002090335,4|0 +395,36,1845422,2,0,B|392:124|468:178,1,167.000002090335,4|8 +492,96,1846192,2,0,B|368:205,1,167.000002090335,2|2 +292,172,1846961,1,8 +152,104,1847217,6,0,B|116:188|17:185,1,167.000002090335,2|0 +8,268,1847986,2,0,B|85:296,1,83.5000010451675,8|0 +221,264,1848499,2,0,B|143:235,1,83.5000010451675,2|0 +220,344,1849012,2,0,B|299:374,1,83.5000010451675,8|0 +432,356,1849525,5,4 +508,328,1849781,2,0,B|488:288|445:276,1,83.5000010451675,0|8 +364,268,1850294,2,0,B|358:176|289:122,1,167.000002090335 +352,68,1851063,2,0,B|296:8,1,83.5000010451675,8|0 +216,7,1851576,2,0,B|160:68,1,83.5000010451675,2|0 +80,88,1852089,2,0,B|44:168,1,83.5000010451675,8|0 +16,240,1852602,1,2 +32,312,1852858,2,0,B|104:332|124:260|189:280,1,167.000002090335 +323,280,1853627,6,0,B|388:260|408:332|481:311,1,167.000002090335,4|8 +456,240,1854397,2,0,B|388:260|408:332|340:352,1,167.000002090335,0|2 +256,280,1855166,1,8 +148,324,1855422,1,0 +104,220,1855679,1,2 +212,176,1855935,1,0 +256,280,1856192,5,8 +104,220,1856448,1,0 +164,64,1856704,1,2 +316,124,1856961,1,0 +316,124,1857089,1,8 +316,124,1857217,1,8 +420,252,1857474,1,8 +420,252,1857602,1,8 +420,252,1857730,5,4 +476,192,1857986,1,0 +500,272,1858243,2,0,B|496:332|424:344|424:344|372:352,1,167.000002090335,8|2 +300,312,1859012,1,0 +256,240,1859268,1,8 +212,312,1859525,1,0 +137,351,1859781,2,0,B|87:344|87:344|15:332|10:271,1,167.000002090335,4|8 +96,264,1860551,2,0,B|176:244,1,83.5000010451675,0|2 +124,180,1861063,2,0,B|200:148,2,83.5000010451675,0|8|0 +120,96,1861833,6,0,B|176:28|271:47,1,167.000002090335,4|8 +292,160,1862602,1,0 +256,264,1862858,1,2 +220,160,1863115,1,0 +312,224,1863371,1,8 +200,224,1863627,1,0 +243,48,1863884,6,0,B|336:28|393:97,1,167.000002090335,4|8 +460,168,1864653,1,0 +408,252,1864909,1,2 +424,348,1865166,1,0 +324,336,1865422,1,8 +244,280,1865679,1,8 +224,316,1865807,1,8 +188,336,1865935,5,4 +168,256,1866192,2,0,B|212:188|306:172,1,167.000002090335,2|0 +336,96,1866961,2,0,B|376:112|384:160,1,83.5000010451675,2|0 +424,232,1867474,1,8 +344,256,1867730,2,0,B|300:188|206:170,1,167.000002090335,4|0 +128,158,1868499,2,0,B|136:112|176:96,1,83.5000010451675,8|0 +120,12,1869012,2,0,B|112:58|72:74,1,83.5000010451675,2|0 +0,116,1869525,1,8 +40,188,1869781,1,0 +112,148,1870038,1,4 +256,64,1870551,6,0,B|288:132|224:236|256:310,2,250.500003135502,8|2|8 +256,64,1872217,1,0 +380,64,1872474,2,0,B|420:80,1,41.7500005225837 +440,192,1872858,2,0,B|376:164|376:164,1,41.7500005225837 +240,168,1873371,5,8 +204,216,1873499,1,8 +144,224,1873627,1,8 +92,188,1873756,1,0 +88,128,1873884,1,8 +124,76,1874012,1,8 +184,68,1874140,5,4 +292,76,1874397,2,0,B|364:88|360:168|360:168|356:260,1,219.999993443489,2|0 +324,364,1875166,1,2 +256,276,1875422,1,0 +188,364,1875679,1,8 +156,260,1875935,2,0,B|152:168|152:168|148:88|220:76,1,219.999993443489,2|0 +324,104,1876704,1,8 +256,192,1876961,2,0,B|184:104|72:80,1,219.999993443489,2|0 +168,24,1877730,1,8 +256,192,1877986,6,0,B|328:280|440:304,1,219.999993443489,4|0 +352,372,1878756,1,8 +252,328,1879012,2,0,B|216:280|160:276,1,109.999996721745,0|2 +28,300,1879525,2,0,B|64:348|120:352,1,109.999996721745,0|8 +76,224,1880038,1,0 +36,120,1880294,6,0,B|32:76|104:40|120:84|136:128|208:92|196:48,1,219.999993443489,4|8 +204,156,1881063,1,0 +216,208,1881192,2,0,B|228:244|256:244|284:244|296:208,1,109.999996721745,2|0 +308,156,1881576,1,2 +360,24,1881833,1,8 +452,132,1882089,1,2 +476,272,1882345,6,0,B|480:316|408:352|392:308|376:264|304:300|316:344,1,219.999993443489,4|8 +256,252,1883115,1,0 +200,344,1883371,1,2 +108,284,1883627,2,0,B|84:176,1,109.999996721745,0|8 +164,100,1884140,2,0,B|176:208,1,109.999996721745,0|2 +256,132,1884653,2,0,B|256:20,1,109.999996721745,0|8 +348,100,1885166,2,0,B|336:208,1,109.999996721745,0|2 +404,284,1885679,2,0,B|428:176,1,109.999996721745,0|8 +452,68,1886192,1,0 +256,40,1886448,5,4 +152,80,1886704,1,0 +256,196,1886961,1,8 +360,304,1887217,1,2 +256,344,1887474,1,0 +256,196,1887730,1,0 +104,192,1887986,5,8 +144,296,1888243,1,0 +256,196,1888499,1,0 +368,88,1888756,1,2 +408,192,1889012,1,8 +256,196,1889268,1,0 +104,192,1889525,1,2 +144,88,1889781,5,0 +256,40,1890038,1,8 +368,88,1890294,1,8 +396,136,1890422,1,0 +420,184,1890551,1,4 +416,296,1890807,2,0,B|368:312|316:288|316:288|256:256|196:288|196:288|144:312|96:296,1,329.999990165234,2|0 +148,200,1891833,1,0 +256,176,1892089,1,8 +403,79,1892345,6,0,B|456:100|456:100|432:152|456:204|456:204|403:224,1,219.999993443489,4|0 +256,256,1893115,1,8 +109,224,1893371,2,0,B|56:204|56:204|80:152|56:100|56:100|108:80,1,219.999993443489,2|0 +256,48,1894140,1,8 +344,196,1894397,6,0,B|300:264|204:264|160:196,1,219.999993443489,4|0 +152,84,1895166,1,8 +256,48,1895422,1,0 +360,84,1895679,1,2 +440,160,1895935,2,0,B|456:212,1,54.9999983608723,0|2 +412,312,1896320,2,0,B|396:260,1,54.9999983608723,0|2 +256,176,1896704,5,4 +208,328,1896961,1,0 +332,232,1897217,1,8 +180,232,1897474,1,0 +304,328,1897730,1,2 +256,256,1897986,1,0 +116,308,1898243,5,8 +80,264,1898371,1,0 +72,208,1898499,1,2 +84,156,1898627,1,0 +116,112,1898756,1,4 +312,12,1899268,5,8 +412,52,1899525,1,0 +324,120,1899781,1,2 +428,156,1900038,1,0 +428,336,1900294,5,8 +360,248,1900551,2,0,B|292:204|220:316|152:268,1,219.999993443489,4|0 +80,184,1901320,1,8 +188,160,1901576,1,0 +192,104,1901704,1,8 +196,48,1901833,1,8 +316,48,1902089,5,8 +320,104,1902217,1,8 +324,160,1902345,1,0 +340,212,1902474,1,8 +376,256,1902602,1,8 +424,280,1902730,1,0 +480,288,1902858,1,4 +256,132,1917730,5,0 +316,192,1917986,1,0 +256,252,1918243,1,8 +196,192,1918499,1,0 +256,92,1918756,5,2 +360,272,1919012,1,0 +152,272,1919268,1,4 +80,232,1919525,6,0,B|93:151,1,82.9999969327451,0|2 +56,47,1920038,2,0,B|16:120,2,82.9999969327451,0|8|0 +136,32,1920807,1,2 +208,68,1921063,1,0 +352,100,1921320,6,0,B|388:112|432:92|432:92|420:140|436:176,1,165.99999386549,4|2 +424,256,1922089,2,0,B|452:288|496:292,2,82.9999969327451,0|8|0 +360,204,1922858,2,0,B|312:272,1,82.9999969327451,2|0 +208,360,1923371,6,0,B|147:316|111:364|49:325,1,165.99999386549,4|2 +60,240,1924140,2,0,B|108:240|132:208,1,82.9999969327451,0|8 +148,88,1924653,2,0,B|152:132|193:153,1,82.9999969327451,0|2 +212,232,1925166,2,0,B|180:308,1,82.9999969327451 +256,336,1925679,1,0 +332,308,1925935,6,0,B|299:231,1,82.9999969327451,0|0 +312,148,1926448,1,8 +404,60,1926704,1,0 +376,180,1926961,1,2 +328,68,1927217,1,0 +436,132,1927474,1,4 +136,180,1927986,5,2 +108,60,1928243,1,0 +200,140,1928499,1,8 +76,132,1928756,1,0 +184,68,1929012,1,2 +136,180,1929268,1,0 +176,296,1929525,6,0,B|340:296,1,165.99999386549,4|2 +400,236,1930294,2,0,B|330:191,1,82.9999969327451,0|8 +256,152,1930807,1,0 +181,191,1931063,2,0,B|112:236,1,82.9999969327451,2|0 +80,128,1931576,6,0,B|112:48|112:48|188:24,1,165.99999386549,4|2 +256,100,1932345,1,0 +324,24,1932602,2,0,B|400:48|400:48|432:128,1,165.99999386549,8|2 +408,204,1933371,1,0 +388,240,1933499,1,0 +356,268,1933627,1,0 +308,200,1933884,1,0 +256,264,1934140,1,0 +204,200,1934397,1,0 +256,136,1934653,1,8 +104,204,1934909,5,0 +124,240,1935038,1,0 +156,268,1935166,1,0 +256,264,1935422,1,0 +256,280,1935679,1,0 +136,128,1950038,6,0,B|134:99|134:99,5,27.6666656442484 +208,128,1950551,2,0,B|208:95|208:95,5,27.6666656442484 +280,128,1951063,2,0,B|280:97|280:97,5,27.6666656442484 +352,128,1951576,2,0,B|352:99|352:99,5,27.6666656442484 +404,184,1952089,6,0,B|384:244|304:256|304:256|216:268,1,219.999993443489,4|8 +104,280,1952858,1,0 +128,328,1952986,2,0,B|188:328|228:364,1,109.999996721745,2|0 +287,368,1953371,2,0,B|328:332|388:332,1,109.999996721745,2|8 +356,228,1953884,6,0,B|384:184|356:124|356:124|384:68|356:16,1,219.999993443489,4|0 +256,60,1954653,1,8 +155,16,1954909,2,0,B|128:68|156:124|156:124|128:184|156:228,1,219.999993443489,2|0 +256,184,1955679,1,8 +448,128,1955935,6,0,B|468:208|416:288|348:296,1,219.999993443489,4|0 +176,200,1956704,1,8 +256,52,1956961,1,0 +336,200,1957217,1,2 +176,104,1957474,1,0 +336,104,1957730,1,8 +256,248,1957986,1,0 +64,127,1958243,6,0,B|44:208|96:288|164:296,1,219.999993443489,4|8 +208,196,1959012,1,2 +256,168,1959140,1,0 +304,196,1959268,1,2 +388,80,1959525,1,0 +256,20,1959781,1,8 +124,80,1960038,1,2 +76,204,1960294,5,4 +84,336,1960551,2,0,B|112:288,1,54.9999983608723,2|0 +160,244,1960807,1,10 +256,152,1961063,2,0,B|256:208,1,54.9999983608723,2|0 +256,272,1961320,1,2 +428,336,1961576,2,0,B|400:288,1,54.9999983608723,2|0 +352,244,1961833,1,10 +256,332,1962089,1,0 +160,244,1962345,5,2 +216,244,1962474,1,0 +272,232,1962602,1,0 +320,204,1962730,1,0 +344,156,1962858,1,8 +348,100,1962986,1,0 +328,48,1963115,1,0 +284,16,1963243,1,0 +228,16,1963371,5,2 +184,48,1963499,1,0 +164,100,1963627,1,0 +168,156,1963756,1,0 +192,204,1963884,1,8 +240,232,1964012,1,0 +296,244,1964140,1,0 +352,244,1964268,1,0 +408,236,1964397,5,4 +408,124,1964653,1,0 +256,148,1964909,1,8 +316,44,1965166,1,2 +196,44,1965422,1,0 +108,208,1965679,5,2 +256,312,1965935,1,8 +404,208,1966192,1,4 +256,192,1966448,12,4,1968499 +76,64,1969012,5,8 +180,104,1969268,1,0 +140,208,1969525,2,0,B|112:260|56:272,2,109.999996721745,2|0|8 +40,160,1970294,6,0,B|72:276|180:320,1,219.999993443489,4|0 +256,192,1971063,1,8 +332,64,1971320,2,0,B|440:108|472:224,1,219.999993443489,2|0 +256,288,1972089,1,8 +40,223,1972345,6,0,B|72:108|180:64,1,219.999993443489,4|0 +200,172,1973115,2,0,B|312:172,2,109.999996721745,8|0|2 +148,268,1973884,1,0 +204,272,1974012,2,0,B|256:288|256:288|308:272,1,109.999996721745,2|0 +364,268,1974397,1,2 +190,203,1974653,6,0,B|116:168|116:64|192:28,1,219.999993443489,4|8 +256,116,1975422,1,0 +320,28,1975679,2,0,B|396:64|396:168|320:204,1,219.999993443489,4|8 +256,292,1976448,1,0 +168,360,1976704,6,0,B|204:284|308:284|344:360,2,219.999993443489,4|8|2 +144,252,1977986,1,0 +40,216,1978243,1,8 +56,324,1978499,1,4 +256,44,1979012,1,0 +456,324,1979525,1,2 +192,244,1980038,5,8 +180,192,1980166,1,0 +204,144,1980294,1,8 +256,124,1980422,1,0 +308,144,1980551,1,2 +332,192,1980679,1,0 +320,244,1980807,1,4 +256,64,1994397,5,2 +336,320,1994825,1,0 +128,160,1995254,1,0 +384,160,1995682,1,0 +176,320,1996111,1,0 +256,192,1996539,1,0 +336,320,1996968,1,2 +336,144,1997397,6,0,B|320:101|256:69|192:101|176:149,1,200 +384,32,1998254,1,2 +256,192,1998682,1,0 +128,32,1999111,1,0 +176,240,1999539,2,0,B|192:283|256:315|320:283|336:235,1,200,2|0 +128,352,2000397,5,0 +256,192,2000825,1,0 +384,352,2001254,1,0 +472,200,2001682,2,0,B|456:96|456:96|384:32,1,200,0|2 +213,100,2002539,2,0,B|192:128|208:176|255:196|304:176|320:128|296:95,1,200 +126,33,2003397,2,0,B|56:96|56:96|40:200,1,200,2|0 +176,352,2004254,6,0,B|200:312|256:296|256:296|312:312|336:352,1,200,0|2 +301,182,2005111,1,0 +213,182,2005325,1,0 +304,24,2005754,2,0,B|349:50|349:50|368:96,1,100,0|2 +208,24,2006397,2,0,B|163:50|163:50|144:96,1,100 +339,233,2007254,6,0,B|380:288|348:384|255:424|157:384|125:288|173:223,1,400,2|0 +86,73,2008539,2,0,B|108:33|161:17|214:22|256:77|256:77|299:21|352:13|416:31|429:79|429:79,1,400,2|0 +336,248,2009825,2,0,B|320:202|256:170|192:202|176:245,1,200,2|0 +176,336,2010468,2,0,B|191:381|255:413|319:381|335:338,1,200 +256,296,2011111,5,2 +336,136,2011539,1,0 +176,136,2011968,1,0 +256,176,2012182,1,0 +336,136,2012397,2,0,B|368:80|336:32|304:16|304:16|256:52|256:52|208:16|208:16|176:32|136:80|180:144,1,400,2|0 +94,276,2013682,2,0,B|134:359|222:383|262:386|300:380|377:360|420:275,1,400,2|0 +256,192,2014968,5,2 +104,112,2015397,1,0 +184,64,2015611,2,0,B|199:18|263:-13|327:18|343:61,1,200 +408,112,2016254,1,2 +256,192,2016682,5,0 +168,360,2017111,1,0 +256,320,2017325,1,0 +340,360,2017539,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,1,600,2|2 +32,240,2019254,2,0,B|32:140,1,100 +104,84,2019682,1,0 +176,248,2020111,2,0,B|200:288|256:304|256:304|312:288|336:248,1,200,2|0 +336,156,2020754,2,0,B|312:116|256:100|256:100|200:116|176:156,1,200 +256,200,2021397,5,2 +356,348,2021825,1,0 +480,240,2022254,2,0,B|480:140,1,100,2|0 +408,84,2022682,1,0 +300,228,2023111,6,0,B|321:256|305:304|258:324|209:304|193:256|217:223,1,200,0|2 +256,148,2023754,1,0 +256,60,2023968,1,0 +100,84,2024397,1,2 +100,84,2024611,1,0 +100,84,2024825,1,0 +175,247,2025254,2,0,B|190:293|254:324|318:293|334:250,2,200,2|0|0 +342,361,2026539,5,2 +256,200,2026968,1,0 +256,200,2027182,1,0 +169,361,2027611,1,0 +169,361,2027825,1,2 +64,203,2028254,6,0,B|92:107|92:107|168:43,1,200,0|2 +343,42,2029111,2,0,B|420:107|420:107|448:203,1,200 +336,339,2029968,1,2 +256,312,2030182,1,0 +176,340,2030397,2,0,B|160:282|194:232|256:224|256:224|296:208|328:160|296:112|256:104|216:112|176:160|216:208|256:224|256:224|320:232|355:286|334:348,1,600,0|2 +440,192,2032111,5,0 +344,40,2032539,1,0 +256,16,2032754,1,0 +168,40,2032968,1,2 +72,192,2033397,1,0 +211,312,2033825,2,0,B|190:340|206:388|253:408|302:388|318:340|294:307,1,200,0|2 +208,156,2034682,5,0 +256,24,2034897,1,0 +304,156,2035111,1,2 +184,72,2035325,1,0 +324,76,2035539,1,0 +408,236,2035968,5,2 +256,340,2036397,1,0 +104,236,2036825,1,2 +172,68,2037254,1,0 +172,68,2037468,1,0 +340,68,2037897,1,0 +340,68,2038111,1,0 +256,200,2038539,5,2 +336,360,2038968,2,0,B|312:320|256:304|256:304|200:320|176:360,1,200,0|2 +104,196,2039825,1,0 +256,100,2040254,1,2 +408,200,2040682,1,0 +256,192,2040897,12,0,2043254 +256,24,2044325,5,2 +301,102,2044539,2,0,B|322:130|306:178|259:198|210:178|194:130|218:97,1,200 +340,232,2045397,1,2 +256,260,2045611,1,0 +172,232,2045825,1,4 +76,360,2046254,1,0 +436,360,2047111,1,2 +334,144,2047539,6,0,B|320:183|265:230|188:199|179:142|179:142,1,200,0|2 +212,52,2048182,2,0,B|224:38|241:23|296:26|305:57|305:57,1,100 +64,336,2049682,5,2 +173,204,2050111,2,0,B|187:236|247:285|320:257|341:200|341:200,1,200,0|2 +374,109,2050754,2,0,B|347:44|270:-14|156:19|137:117|137:117,1,300 +448,336,2052254,1,2 +256,192,2052682,5,0 +192,250,2052897,2,0,B|157:294|184:334|233:346|256:312|256:312|282:348|328:336|364:292|309:241,2,300,0|2|0 +56,324,2054611,1,0 +56,324,2054825,5,2 +176,96,2055254,2,0,B|200:56|256:40|256:40|312:56|336:96,1,200,0|2 +256,144,2055897,1,0 +336,188,2056111,2,0,B|312:228|256:244|256:244|200:228|176:188,1,200 +344,356,2057397,2,0,B|312:320|276:336|256:356|256:356|236:332|192:320|160:368,1,200,2|0 +256,264,2058039,1,0 +359,34,2058682,6,0,B|413:71|379:165|309:209|236:120|188:73|200:24|245:-2|264:-12|283:-2|302:16|321:73|283:120|231:211|109:185|106:73|123:44|171:20,1,600,2|2 +256,208,2060397,1,0 +212,372,2060825,1,2 +300,372,2061039,1,0 +136,168,2061682,6,0,B|163:233|240:291|354:258|373:160|373:160,1,300,0|0 +337,73,2062539,2,0,B|323:41|263:-8|190:20|169:77|169:77,1,200,2|0 +254,129,2063182,1,0 +176,352,2063825,1,2 +256,316,2064039,1,0 +336,352,2064254,1,0 +256,192,2064468,12,0,2065968 +46,270,2066397,6,0,B|106:194|106:194|46:110,1,200,2|0 +299,34,2067254,2,0,B|320:62|304:110|257:130|208:110|192:62|216:29,1,200,2|0 +454,270,2068111,2,0,B|394:194|394:194|454:110,1,200,2|0 +298,350,2068968,2,0,B|319:322|303:274|256:254|207:274|191:322|215:355,1,200,4|0 +480,60,2070254,6,0,B|480:107|412:116|391:71|391:71|372:26|320:26|301:71|301:71|284:116|230:116|211:71|211:71|194:26|140:26|123:71|123:71|99:116|33:107|33:58,2,600 +256,200,2073254,5,0 +168,360,2073682,1,0 +340,360,2074111,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,2,600,8|8|8 +472,200,2077325,6,0,B|472:100,1,100,8|8 +392,56,2077754,1,0 +336,128,2077968,2,0,B|312:168|256:184|256:184|200:168|176:128,1,200,8|0 +40,200,2078825,2,0,B|40:100,1,100,8|0 +116,52,2079254,1,4 +177,271,2079682,6,0,B|201:231|257:215|257:215|313:231|337:271,1,200 +299,355,2080325,2,0,B|283:372|256:377|256:377|227:372|213:354,1,100,0|8 +144,90,2080968,6,0,B|153:55|199:44|221:77|199:100|209:146|242:133|255:48|255:48|265:133|298:146|310:100|288:77|310:44|364:50|366:88,1,400,0|8 +340,268,2082254,1,0 +256,304,2082468,1,8 +172,268,2082682,1,0 +328,360,2083111,5,8 +256,196,2083539,1,0 +184,360,2083968,1,0 +112,194,2084397,2,0,B|172:135|127:69|127:69|216:94|257:0|257:0|292:94|382:69|382:69|339:136|399:194,2,600,8|8|8 +206,348,2087397,6,0,B|310:348,1,100,8|8 +355,271,2087825,2,0,B|154:271,1,200,8|8 +208,40,2088682,6,0,B|312:40,1,100,8|8 +357,117,2089111,2,0,B|156:117,1,200,8|4 +52,260,2089968,1,0 +212,344,2090397,2,0,B|224:358|241:373|296:370|305:339|305:339,1,100,0|2 +256,268,2090825,1,8 +460,260,2091254,1,0 +416,84,2091682,5,2 +352,148,2091897,1,0 +328,60,2092111,1,8 +160,148,2092539,1,0 +96,84,2092754,1,8 +184,60,2092968,1,0 +129,290,2093397,6,0,B|157:341|243:400|353:360|384:290|384:290,1,300,8|0 +300,228,2094254,2,0,B|288:242|271:257|216:254|207:223|207:223,1,100,2|0 +256,144,2094682,1,8 +148,44,2095111,6,0,B|72:112|72:112|56:208,1,200 +80,288,2095754,1,0 +168,324,2095968,2,0,B|192:360|232:360|256:344|256:344|276:360|320:360|344:324,1,200,8|0 +456,208,2096825,2,0,B|440:112|440:112|364:44,1,200,2|8 +256,192,2097682,1,0 +301,268,2097897,2,0,B|256:288|256:288|210:268,1,100,8|0 +48,344,2098539,5,8 +192,48,2099182,2,0,B|160:89|184:161|254:190|327:161|351:89|315:40,1,300,2|8 +464,344,2100468,1,0 +256,272,2100897,5,2 +256,360,2101111,1,8 +172,208,2101539,2,0,B|186:176|246:127|319:155|340:212|340:212,1,200,0|0 +123,97,2102397,2,0,B|158:52|257:5|353:49|389:100,1,300,8|8 +388,99,2103254,1,0 +332,296,2103682,1,8 +256,340,2103897,1,0 +180,296,2104111,1,0 +176,208,2104325,1,0 +256,160,2104539,1,0 +336,208,2104754,1,0 +256,248,2104968,5,8 +256,36,2105611,1,0 +256,248,2106039,5,2 +176,304,2106254,2,0,B|200:344|256:360|256:360|312:344|336:304,1,200,8|0 +411,139,2107111,2,0,B|370:42,1,100,2|0 +299,99,2107539,2,0,B|320:127|304:175|257:195|208:175|192:127|216:94,1,200,8|0 +139,46,2108182,2,0,B|101:139,1,100,8|0 +128,288,2108825,2,0,B|256:368|256:368|384:288,1,300,8|8 +300,232,2109682,2,0,B|256:256|256:256|212:232,1,100,8|0 +256,152,2110111,1,4 +108,52,2110539,5,2 +204,204,2110968,2,0,B|210:255|210:255|254:287|302:255|302:255|306:203,1,200,0|8 +412,60,2111825,1,2 +412,60,2112039,1,0 +440,224,2112468,1,0 +440,224,2112682,2,0,B|416:324|416:324|328:368|328:368,1,200,8|2 +184,368,2113539,2,0,B|97:324|97:324|73:224,1,200,0|8 +176,76,2114397,2,0,B|200:116|256:132|256:132|312:116|336:76,1,200,2|8 +256,32,2115039,1,0 +256,32,2115254,5,4 +432,192,2115682,2,0,B|404:171|356:187|336:234|356:283|404:299|437:275,1,200,2|0 +256,360,2116539,1,8 +80,276,2116968,2,0,B|108:297|156:281|176:234|156:185|108:169|75:193,1,200,2|0 +176,40,2117825,5,8 +336,40,2118254,1,0 +256,96,2118468,1,8 +176,40,2118682,1,0 +256,200,2119111,1,8 +208,348,2119539,5,8 +176,260,2119754,1,8 +256,200,2119968,1,8 +336,260,2120182,1,0 +304,348,2120397,1,12 +348,72,2120825,6,0,B|288:72|288:72|256:48|256:48|224:72|224:72|164:72,1,200,2|0 +160,352,2121682,6,0,B|128:338|79:278|107:205|164:184|164:184,1,200,8|2 +256,172,2122325,2,0,B|256:372,1,200,0|2 +352,352,2122968,2,0,B|384:338|433:278|405:205|348:184|348:184,1,200,8|0 +256,32,2123825,5,2 +96,152,2124254,1,8 +164,336,2124682,1,2 +348,336,2125111,1,0 +416,152,2125539,1,0 +256,192,2125647,12,0,2127682 +348,80,2127897,6,0,B|288:80|288:80|256:56|256:56|224:80|224:80|164:80,1,200,2|0 +172,288,2128754,2,0,B|186:256|246:207|319:235|340:292|340:292,1,200,0|0 +256,344,2129397,1,8 +192,152,2129825,2,0,B|156:56,1,100,8|0 +256,68,2130254,1,8 +320,152,2130468,2,0,B|356:56,1,100,0|4 +426,311,2131111,6,0,B|404:351|351:367|298:362|256:307|256:307|213:363|160:371|96:353|83:305|83:305,1,400,0|8 +256,192,2132397,5,0 +340,32,2132825,1,2 +256,64,2133039,1,0 +172,32,2133254,1,8 +256,284,2133897,1,8 +182,323,2134111,2,0,B|256:387|256:387|336:323,1,200,0|8 +320,152,2134968,2,0,B|292:208|292:208|256:188|220:208|220:208|196:152,1,200 +144,76,2135611,2,0,B|196:64|232:20|232:20|256:56|256:56|276:20|276:20|320:64|368:76,1,300 +404,252,2136682,5,0 +256,356,2137111,1,8 +108,252,2137539,1,0 +164,172,2137754,2,0,B|224:140|256:172|304:220|288:268|224:268|208:220|256:172|288:140|348:172,2,300,0|2|0 +256,28,2139468,5,0 +256,28,2139682,1,8 +396,140,2140111,1,8 +344,216,2140325,1,8 +256,240,2140539,1,0 +168,216,2140754,1,8 +116,140,2140968,1,12 +113,306,2141397,2,0,B|128:352|192:368|224:336|224:288|184:272|152:320|204:368|256:368|256:368|308:368|360:320|328:272|288:288|288:336|324:368|400:344|403:296,1,456.000004348755,0|8 +256,144,2142682,1,0 +256,56,2142897,1,0 +344,360,2143539,6,0,B|294:310|319:215|378:210|418:265|378:315|304:290|254:225|254:225|204:290|129:315|89:265|129:210|189:215|214:310|164:360,1,600,0|8 +172,68,2145254,2,0,B|186:36|246:-13|319:15|340:72|340:72,1,200,0|2 +373,163,2145897,2,0,B|346:228|269:286|155:253|136:155|136:155,2,300,0|0|2 +440,224,2147397,5,8 +213,335,2147825,2,0,B|236:352|256:360|256:360|277:351|304:331,1,100,0|2 +76,228,2148682,1,8 +256,192,2148789,12,2,2151254 +60,85,2151682,6,0,B|32:134|31:182,1,100,2|8 +120,156,2152111,2,0,B|98:269|152:358,1,200,0|8 +451,298,2152968,6,0,B|479:249|480:201,1,100,8|8 +391,227,2153397,2,0,B|413:114|359:25,1,200,8|4 +256,192,2154254,5,0 +33,334,2155111,6,0,B|33:287|101:278|122:323|122:323|141:368|193:368|212:323|212:323|229:278|283:278|302:323|302:323|319:368|373:368|390:323|390:323|414:278|480:287|480:336,1,600,8|8 +256,152,2157039,1,8 +176,108,2157254,2,0,B|192:65|256:33|320:65|336:113,2,200,8|8|8 +256,264,2158539,1,8 +80,336,2158968,6,0,B|132:368|160:336|176:320|176:320|192:336|208:352|232:344|232:344|256:368|280:344|280:344|304:352|320:336|336:320|336:320|352:336|380:368|432:336,1,400,8|0 +340,172,2160254,2,0,B|381:116|349:21|256:-18|159:21|127:116|175:181,1,400,8|0 +157,353,2161539,2,0,B|358:353,1,200,8|0 +306,276,2162182,2,0,B|202:276,1,100,8|0 +56,176,2162825,5,8 +157,31,2163254,2,0,B|358:31,1,200,0|8 +306,108,2163897,2,0,B|202:108,1,100,0|4 +256,280,2164539,1,0 +456,176,2164968,5,0 +380,228,2165182,1,0 +464,268,2165397,1,8 +300,364,2165825,2,0,B|321:336|305:288|258:268|209:288|193:336|217:369,1,200 +256,192,2166682,1,8 +56,176,2167111,1,0 +132,228,2167325,1,8 +48,264,2167539,1,0 +256,192,2167968,5,8 +300,20,2168397,2,0,B|321:48|305:96|258:116|209:96|193:48|217:15,1,200 +97,157,2169254,2,0,B|128:223|176:255|256:301|335:255|383:223|415:160,1,400,8|0 +437,320,2170539,2,0,B|368:384|256:416|139:386|72:316,1,400,8|0 +165,176,2171825,2,0,B|195:207|256:237|316:207|346:176,1,200,8|0 +352,88,2172468,2,0,B|296:0,1,100,8|0 +213,5,2172897,2,0,B|159:89,1,100,8|8 +48,232,2173539,5,8 +24,320,2173754,1,0 +104,360,2173968,1,8 +176,304,2174182,1,0 +136,224,2174397,1,4 +76,56,2174825,2,0,B|128:20|112:100|207:51|191:51|191:51|223:83|255:50|255:50|287:83|330:51|314:51|314:51|384:100|384:20|442:59,1,400,0|8 +464,232,2176111,5,8 +488,320,2176325,1,0 +408,360,2176539,1,8 +336,304,2176754,1,0 +376,224,2176968,1,8 +312,56,2177397,2,0,B|416:104|416:104,1,100 +200,56,2178039,2,0,B|96:104|96:104,1,100,0|8 +176,264,2178682,2,0,B|232:272|256:216|256:216|280:272|336:264,1,200 +344,360,2179325,1,0 +344,360,2179539,2,0,B|312:396|276:380|256:360|256:360|236:384|192:396|160:348,2,200,4|0|0 +367,36,2180825,6,0,B|398:21|475:23|512:92|491:149|491:149,1,200,8|2 +456,240,2181468,2,0,B|392:271|296:264|232:164|282:78|282:78,1,300,0|2 +256,336,2182754,1,0 +256,336,2182968,1,0 +256,152,2183397,1,8 +145,36,2183825,2,0,B|114:21|37:23|0:92|21:149|21:149,1,200,8|2 +56,240,2184468,2,0,B|120:271|216:264|280:164|230:78|230:78,1,300,0|2 +388,164,2185539,5,0 +256,360,2185968,1,8 +124,164,2186397,1,2 +180,308,2186825,1,0 +256,272,2187039,1,0 +332,308,2187254,1,8 +256,24,2187897,5,8 +256,216,2188325,1,0 +336,172,2188539,2,0,B|312:132|256:116|256:116|200:132|176:172,2,200,8|0|0 +428,337,2189825,2,0,B|397:384|334:368|272:337|319:306|319:259|303:243|256:228|209:243|194:259|194:306|241:337|178:368|116:384|85:337,1,456.000004348755,4|0 +16,176,2191111,2,0,B|59:186|103:161|113:118|113:118|114:83|115:49|81:30|46:46|28:80|44:116|78:117|113:118|113:118|157:111|184:69|177:25,1,456.000004348755,8|0 +334,24,2192397,2,0,B|327:69|354:111|398:118|398:118|433:117|467:116|483:80|465:46|430:30|396:49|397:83|398:118|398:118|408:161|452:186|496:176,1,456.000004348755,8|0 +464,254,2193468,1,0 +404,328,2193682,5,8 +256,192,2194111,1,0 +176,124,2194325,1,8 +208,36,2194539,1,8 +304,36,2194754,1,0 +336,124,2194968,1,12 +96,226,2195397,2,0,B|112:308|179:376|252:395|355:368|404:296|422:226,1,456.000004348755,0|8 +256,104,2196682,5,0 +44,48,2197111,1,0 +104,116,2197325,1,0 +176,56,2197539,2,0,B|192:13|256:-19|320:13|336:61,1,200,8|0 +336,148,2198182,2,0,B|320:191|256:223|192:191|176:143,1,200,8|0 +256,104,2198825,5,8 +164,300,2199254,1,0 +348,300,2199682,1,0 +256,192,2200111,12,8,2201397 +256,192,2201504,12,8,2202682 +256,192,2202789,12,4,2205254 +384,80,2225825,5,4 +128,80,2226468,1,2 +132,308,2227111,1,8 +384,308,2227754,1,2 +256,192,2228182,5,0 +336,148,2228397,1,8 +336,60,2228611,1,0 +256,24,2228825,1,2 +176,60,2229039,1,0 +176,148,2229254,1,0 +256,192,2229468,1,0 +320,256,2229682,2,0,B|351:297|327:368|257:398|185:368|161:297|196:248,1,300,8|2 +108,228,2230539,5,0 +72,208,2230682,1,0 +52,168,2230825,1,0 +56,128,2230968,2,0,B|64:76|112:28|176:36|212:76|212:76,1,200,8|0 +288,120,2231611,2,0,B|348:140|388:104|388:104,1,100,2|0 +468,96,2232039,1,0 +440,180,2232254,2,0,B|395:224|395:268|395:313|440:357,1,200,8|2 +256,364,2233111,2,0,B|256:156,1,200,0|8 +72,180,2233968,2,0,B|116:224|116:268|116:313|72:357,2,200,2|0|8 +48,136,2234968,1,0 +56,88,2235111,1,0 +88,60,2235254,2,0,B|112:32|148:32|172:68|172:68|196:100|236:104|260:72,1,200,2|0 +344,48,2235897,5,0 +428,80,2236111,2,0,B|496:88|508:160|492:220|440:240|372:224|372:224,1,200,4|0 +452,308,2236754,2,0,B|404:328|344:296|328:244|328:204|356:176|356:176,1,200,2|0 +336,100,2237397,2,0,B|312:60|256:44|256:44|200:60|176:100,1,200,8|0 +164,188,2238039,2,0,B|156:176|184:204|184:244|168:296|108:328|60:308,1,200,2|0 +76,220,2238682,1,8 +88,132,2238897,1,0 +172,100,2239111,5,2 +212,109,2239218,1,0 +245,142,2239325,1,0 +304,208,2239539,1,0 +388,172,2239754,1,0 +468,212,2239968,2,0,B|500:248|500:304|468:364|388:344|388:344,1,200,8|2 +368,328,2240539,1,0 +345,310,2240682,1,0 +314,311,2240825,1,0 +285,326,2240968,1,0 +252,331,2241111,1,0 +230,317,2241254,2,0,B|193:288|193:230|252:200|252:200|310:171|310:113|252:80,1,300,4|2 +176,48,2242111,6,0,B|136:92|80:76|80:76,1,100,0|0 +80,156,2242539,1,8 +168,324,2242968,5,2 +344,324,2243397,1,0 +432,156,2243825,1,4 +256,32,2244254,1,2 +176,76,2244468,2,0,B|192:119|256:151|320:119|336:71,1,200,8|0 +407,131,2245111,5,0 +388,154,2245218,1,0 +366,175,2245325,1,0 +341,192,2245432,1,0 +313,202,2245539,1,0 +283,210,2245647,1,0 +256,212,2245754,1,0 +229,210,2245861,1,0 +199,202,2245968,1,0 +171,192,2246075,1,0 +146,175,2246182,1,0 +124,154,2246289,1,0 +105,131,2246397,1,4 +52,240,2246825,5,2 +140,320,2247254,1,0 +256,348,2247682,1,8 +372,320,2248111,1,2 +460,240,2248539,1,0 +488,156,2248754,1,0 +404,172,2248968,6,0,B|320:128|320:128|304:20,1,200,8|2 +207,23,2249611,1,0 +207,23,2249825,2,0,B|192:128|192:128|108:172,1,200,0|8 +336,292,2250682,6,0,B|322:324|262:373|189:345|168:288|168:288,1,200,2|0 +135,197,2251325,2,0,B|162:132|239:74|353:107|372:205|372:205,1,300,2|0 +152,36,2252397,1,2 +360,36,2252825,1,8 +256,192,2253254,5,0 +192,250,2253468,2,0,B|157:294|184:334|233:346|256:312|256:312|282:348|328:336|364:292|309:241,2,300,2|8|0 +56,324,2255182,5,2 +56,324,2255397,1,8 +176,96,2255825,2,0,B|200:56|256:40|256:40|312:56|336:96,1,200,2|0 +256,144,2256468,1,0 +336,188,2256682,2,0,B|312:228|256:244|256:244|200:228|176:188,1,200,4|0 +456,324,2257539,1,0 +256,364,2257968,1,8 +56,324,2258397,1,2 +92,232,2258611,6,0,B|140:232|152:194|150:166|150:166|182:167|220:143|206:87,1,200,0|0 +303,101,2259254,2,0,B|298:142|329:167|361:166|361:166|359:194|371:232|420:232,1,200,2|0 +376,312,2259897,2,0,B|324:324|324:324|300:372,1,100,8|0 +208,365,2260325,2,0,B|188:324|188:324|136:312,1,100,0|8 +56,128,2260968,5,2 +256,104,2261397,1,0 +304,20,2261611,1,0 +208,20,2261825,1,8 +372,132,2262254,2,0,B|345:197|268:255|154:222|135:124|135:124,1,300,2|0 +56,188,2263111,2,0,B|76:240|144:300|220:308,1,200,8|0 +304,306,2263754,2,0,B|372:294|436:240|456:188,1,200,2|0 +456,187,2264397,1,2 +256,192,2264504,12,8,2265682 +256,192,2265789,12,10,2266968 +184,124,2267397,5,8 +256,76,2267611,1,8 +328,124,2267825,1,8 +304,212,2268039,1,0 +212,212,2268254,1,8 +256,352,2268682,5,8 +80,68,2269111,1,8 +432,64,2269539,1,12 +466,303,2269968,2,0,B|466:182|332:145|256:220|256:220|142:315|199:391|256:411|313:391|370:315|256:220|256:220|180:145|9:172|46:321|46:321,1,1000,0|8 +256,32,2272754,1,8 +176,76,2272968,2,0,B|192:119|256:151|320:119|336:71,1,200,8|8 +256,200,2273825,5,0 +168,360,2274254,1,0 +340,360,2274682,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,2,600,8|8|8 +472,200,2277897,6,0,B|472:100,1,100,8|8 +392,56,2278325,1,8 +336,128,2278539,2,0,B|312:168|256:184|256:184|200:168|176:128,1,200,4|0 +40,200,2279397,2,0,B|40:100,1,100,2|0 +116,52,2279825,1,4 +177,271,2280254,6,0,B|201:231|257:215|257:215|313:231|337:271,1,200 +299,355,2280897,2,0,B|283:372|256:377|256:377|227:372|213:354,1,100,0|8 +144,90,2281539,6,0,B|153:55|199:44|221:77|199:100|209:146|242:133|255:48|255:48|265:133|298:146|310:100|288:77|310:44|364:50|366:88,1,400,0|8 +340,268,2282825,5,0 +256,304,2283039,1,8 +172,268,2283254,1,0 +328,360,2283682,5,8 +256,196,2284111,1,0 +184,360,2284539,1,0 +256,196,2284968,5,4 +256,192,2285075,12,0,2286254 +256,192,2286361,12,0,2287539 +256,192,2287647,12,0,2290111 +192,144,2308992,6,0,B|160:96|96:80,2,100,2|0|0 +256,112,2309663,2,0,B|256:0,2,100,2|0|0 +320,144,2310335,2,0,B|352:96|416:80,2,100,2|0|0 +256,192,2311006,1,0 +176,304,2311678,2,0,B|208:352|256:368|256:368|304:352|336:304,1,200,4|4 +416,128,2312574,6,0,B|416:64|352:48,2,100,2|0|0 +256,144,2313245,2,0,B|256:32,2,100,2|0|0 +96,128,2313917,2,0,B|96:64|160:48,2,100,2|0|0 +256,144,2314589,1,0 +164,336,2315260,2,0,B|208:305|256:345|256:345|304:305|348:335|348:335,1,200,4|4 +256,256,2316156,5,0 +208,176,2316380,1,0 +112,192,2316603,1,2 +64,112,2316827,2,0,B|112:96|144:48,2,100,0|8|0 +192,112,2317499,2,0,B|256:32|384:48,1,200,2|0 +352,128,2318171,1,0 +336,208,2318394,2,0,B|336:272|400:304,1,100,2|0 +464,304,2318842,2,0,B|464:240|400:208,1,100,8|0 +352,128,2319290,5,2 +208,80,2319514,1,0 +112,192,2319738,2,0,B|64:224|80:288,1,100 +256,256,2320186,2,0,B|208:240|144:256,1,100,2|0 +112,192,2320633,1,8 +80,112,2320857,1,0 +160,80,2321081,2,0,B|256:128|352:80,1,200,2|0 +400,160,2321753,5,0 +352,240,2321977,2,0,B|351:240|307:270|259:230|259:230|211:270|167:239,1,200,2|8 +96,176,2322648,1,0 +64,256,2322872,2,0,B|64:304|80:368|176:368|208:352,1,200,2|4 +192,272,2323544,5,0 +256,208,2323768,1,2 +320,272,2323991,1,0 +311,354,2324215,2,0,B|336:368|432:368|448:304|448:256,1,200,8|0 +416,176,2324887,2,0,B|384:112|416:48,2,100,0|0|0 +320,192,2325559,1,2 +224,192,2325783,2,0,B|224:256|176:288,1,100,0|8 +160,128,2326230,2,0,B|160:64|112:32,1,100,0|2 +224,192,2326678,1,0 +160,128,2326902,1,4 +64,128,2327126,5,0 +192,48,2327350,1,2 +224,192,2327574,1,0 +304,112,2327797,2,0,B|400:48|512:80,1,200,8|2 +464,160,2328469,2,0,B|432:208|432:272,2,100,0|0|0 +464,336,2329141,1,2 +384,304,2329365,1,0 +304,256,2329589,2,0,B|256:288|208:256,1,100,8|0 +124,304,2330036,2,0,B|188:96,1,200,2|4 +156,16,2330708,6,0,B|188:128,1,100,0|2 +272,128,2331156,1,0 +352,176,2331380,2,0,B|304:288|368:384,1,200,8|0 +153,368,2332051,2,0,B|208:288|160:176,1,200 +64,176,2332723,2,0,B|96:224|96:272,1,100,2|0 +192,84,2333171,2,0,B|160:132|160:180,1,100,8|0 +256,192,2333618,1,2 +160,336,2334066,6,0,B|256:384|352:336,2,200,4|0|8 +160,256,2335186,1,2 +160,256,2335409,2,0,B|256:320|352:256,1,200,4|0 +344,168,2336081,6,0,B|360:116|332:72,1,100,0|4 +256,144,2336529,1,0 +256,144,2336641,1,0 +256,144,2336753,1,12 +192,80,2336977,1,0 +128,144,2337200,2,0,B|160:192|128:240,1,100,8|0 +240,336,2337648,6,0,B|192:240|256:144,1,200,4|2 +320,96,2338320,2,0,B|368:64|416:64,2,100,0|8|0 +380,164,2338991,1,0 +380,164,2339215,2,0,B|300:248|180:204,1,200,2|2 +284,108,2339887,1,8 +284,108,2340111,2,0,B|304:220|244:304,1,200,8|2 +160,236,2340783,1,2 +64,288,2341006,6,0,B|48:240|64:176,1,100,0|4 +48,16,2341454,2,0,B|64:64|48:128,1,100,0|2 +128,144,2341902,2,0,B|240:112,1,100,0|8 +304,128,2342350,1,0 +256,304,2342797,6,0,B|336:384|464:352,2,200,0|0|0 +381,253,2343917,2,0,B|381:189|333:157,1,100,8|0 +253,205,2344365,2,0,B|269:253|253:317,1,100,2|0 +173,253,2344812,5,4 +32,128,2345036,2,0,B|32:64|80:32,1,100,0|2 +160,80,2345484,2,0,B|144:128|160:192,1,100,0|8 +240,192,2345932,1,0 +288,112,2346156,1,2 +368,272,2346380,6,0,B|384:208|336:176,1,100,0|0 +288,256,2346827,2,0,B|272:320|320:352,1,100,0|2 +288,256,2347275,1,0 +352,176,2347499,1,8 +392,80,2347723,2,0,B|320:80|256:32|256:32|192:80|104:80,1,300,0|4 +80,160,2348618,5,0 +64,256,2348842,2,0,B|192:240,1,100,2|0 +224,320,2349290,2,0,B|96:336,1,100,8|0 +346,300,2349738,2,0,B|273:227|161:243,1,200,2|0 +176,144,2350409,1,0 +224,64,2350633,5,2 +400,48,2350857,1,0 +336,192,2351081,1,0 +256,80,2351305,5,0 +352,64,2351529,1,0 +320,160,2351753,1,0 +240,256,2351977,6,0,B|144:176|208:48,1,224.999993294478,4|2 +140,112,2352872,2,0,B|120:204|27:226,1,149.999995529652,8|0 +48,220,2353544,1,0 +88,300,2353768,1,2 +168,344,2353991,1,8 +256,364,2354215,2,0,B|336:348,1,74.9999977648259,0|0 +420,344,2354663,6,0,B|356:280|404:200,1,149.999995529652,8|2 +464,144,2355335,1,0 +384,108,2355559,2,0,B|316:68|240:120,1,150.000005722046,0|2 +260,208,2356230,1,0 +260,208,2356454,2,0,B|276:112|212:48|132:32|68:80,1,299.999991059304,10|0 +132,144,2357574,6,0,B|168:140|184:188,2,75.0000028610231,8|2|8 +140,328,2358245,2,0,B|140:232|252:200,1,149.999995529652,8|2 +216,212,2358917,1,8 +372,328,2359141,2,0,B|372:232|260:200,1,149.999995529652,4|2 +256,132,2359812,1,0 +184,80,2360036,2,0,B|256:48|256:48|328:80,2,149.999995529652,8|2|0 +108,128,2361156,1,8 +96,216,2361380,6,0,B|224:312,1,149.999995529652,2|8 +256,224,2362051,1,2 +416,216,2362275,2,0,B|288:312,2,149.999995529652,0|4|2 +420,128,2363394,1,8 +336,92,2363618,2,0,B|288:44|224:28|144:44|96:92|80:156,2,299.999991059304,8|0|8 +288,176,2365633,5,0 +160,256,2365857,1,4 +304,320,2366081,1,0 +288,176,2366305,5,6 +224,80,2366529,2,0,B|160:112|96:96,1,100,2|0 +48,48,2366977,2,2,B|16:96|48:160,1,100,2|0 +128,176,2367424,2,2,B|144:240|112:272,1,100,0|2 +160,368,2367872,2,2,B|240:288|224:176,1,200 +128,176,2368544,5,0 +227,187,2368768,2,2,B|259:139|307:123,1,100 +464,48,2369215,2,0,B|432:80|368:96,1,100,2|0 +368,192,2369663,2,2,B|480:224|480:352,1,200,6|2 +384,320,2370335,5,0 +288,336,2370559,1,2 +192,352,2370783,1,0 +96,352,2371006,2,0,B|128:304|96:256,1,100,2|0 +97,258,2371454,2,2,B|0:192|48:64,1,200 +144,80,2372126,1,0 +240,112,2372350,5,0 +336,80,2372574,1,2 +416,128,2372797,1,0 +416,32,2373021,1,2 +416,128,2373245,2,2,B|320:192|304:288,1,200,4|0 +384,320,2373917,2,0,B|288:256|160:304,1,200,2|0 +112,336,2374589,1,0 +80,256,2374812,1,0 +80,256,2375036,2,2,B|48:128|144:80,1,200,2|2 +208,144,2375708,6,2,B|256:160|320:144,1,100,0|2 +432,80,2376156,2,2,B|381:66|333:82,1,100 +240,64,2376603,1,0 +144,64,2376827,2,2,B|64:128|80:272,1,200,6|0 +32,176,2377499,1,0 +32,176,2377723,2,2,B|112:256|32:384,1,200,6|0 +120,296,2378394,2,2,B|196:196|325:267,1,200,0|4 +480,296,2379066,2,0,B|428:304|380:276,1,100 +432,96,2379514,5,0 +336,192,2379738,1,4 +472,220,2379962,1,0 +428,112,2380186,1,4 +352,184,2380409,1,0 +456,208,2380633,1,4 +256,192,2381081,6,2,B|160:112,1,100 +96,112,2381529,1,2 +96,112,2381753,1,0 +48,192,2381977,1,2 +48,192,2382200,2,2,B|204:316,1,200 +288,320,2382872,2,0,B|352:304|352:240,2,100,0|0|2 +288,320,2383544,1,0 +256,144,2383768,6,0,B|256:192|288:256,2,100,2|0|4 +192,64,2384439,2,2,B|184:121|126:149,1,100,2|2 +32,144,2384887,2,0,B|80:192|64:272,1,100,2|0 +128,320,2385335,1,2 +32,320,2385559,1,0 +128,320,2385783,6,2,B|176:224|304:256,1,200,6|0 +416,240,2386454,2,0,B|360:136|240:176,1,200,0|0 +188,92,2387126,1,4 +288,48,2387350,1,0 +368,112,2387574,1,0 +264,168,2387797,5,6 +368,112,2388021,2,2,B|432:96|480:32,2,100,0|2|2 +331,208,2388693,2,2,B|224:272|203:384,1,200,0|2 +208,358,2389365,1,0 +304,360,2389589,2,2,B|277:248|186:198,1,200,6|2 +128,128,2390260,1,0 +164,48,2390484,2,0,B|208:17|256:57|256:57|304:17|348:47|348:47,1,200,2|0 +384,128,2391156,1,2 +128,128,2391603,1,4 +256,192,2391827,12,0,2394066 +256,48,2401678,5,0 +256,48,2401790,1,0 +256,48,2401902,1,0 +256,48,2402014,1,0 +256,48,2402126,1,4 +208,112,2402350,6,0,B|256:144|304:112,1,100,0|2 +344,176,2402797,1,0 +344,176,2403021,2,0,B|312:208|256:224|192:208|152:168,1,200,8|0 +64,160,2403693,1,2 +32,240,2403917,1,0 +160,176,2404141,2,0,B|224:176|256:128,1,100,0|2 +160,96,2404589,2,0,B|128:48|160:-16,1,100,0|8 +240,32,2405036,2,0,B|309:113|453:97,1,200 +432,192,2405708,1,4 +384,272,2405932,5,0 +336,352,2406156,1,2 +256,304,2406380,2,0,B|256:192,1,100,0|8 +176,176,2406827,1,0 +176,176,2407051,2,0,B|192:48|64:32,2,200,2|0|2 +256,144,2408171,1,2 +256,144,2408394,2,0,B|272:32|400:32,1,200,8|2 +448,208,2409066,1,0 +448,208,2409290,6,0,B|432:144|448:96,1,100,4|0 +352,144,2409738,2,0,B|368:192|352:256,1,100,2|0 +272,288,2410186,2,0,B|256:224|272:176,1,100,8|0 +176,224,2410633,2,0,B|192:272|176:336,1,100,2|0 +96,320,2411081,5,0 +64,224,2411305,1,0 +80,128,2411529,2,0,B|176:64,1,100,2|0 +256,96,2411977,1,8 +353,76,2412200,2,0,B|449:12,1,100,2|0 +416,144,2412648,1,2 +416,144,2412872,6,0,B|464:240|416:352,2,200,8|0|8 +336,176,2413991,1,0 +240,176,2414215,1,2 +288,256,2414439,1,0 +240,176,2414663,1,0 +144,176,2414887,1,0 +192,256,2415111,1,10 +240,176,2415335,5,0 +240,176,2415447,1,0 +240,176,2415559,1,12 +144,48,2415783,1,0 +336,48,2416006,1,8 +240,176,2416230,1,0 +240,176,2416454,1,4 +304,240,2416678,5,0 +352,320,2416902,2,0,B|384:272|448:256,1,100,2|0 +432,176,2417350,2,0,B|320:144|320:16,1,200,8|2 +240,48,2418021,2,0,B|208:160|64:160,1,200,0|2 +48,256,2418693,1,2 +96,352,2418917,1,0 +96,352,2419141,2,0,B|144:240|256:240,2,200,8|0|4 +208,352,2420260,5,0 +304,352,2420484,1,2 +400,352,2420708,1,0 +400,352,2420932,2,0,B|352:240|240:240,1,200,8|0 +176,192,2421603,5,0 +144,112,2421827,1,2 +240,128,2422051,1,0 +240,128,2422275,2,0,B|352:128|416:16,1,200,0|8 +464,144,2422947,1,0 +416,272,2423171,1,2 +256,352,2423618,5,4 +160,336,2423842,2,0,B|112:240|176:144,1,200 +112,96,2424514,2,0,B|160:64|208:64,2,100,8|0|2 +288,80,2425186,1,0 +368,128,2425409,1,0 +352,224,2425633,1,0 +432,352,2425857,6,0,B|416:304|448:256,1,100,2|0 +256,208,2426305,2,0,B|256:320,1,100,8|0 +80,352,2426753,2,0,B|96:304|64:256,1,100,2|0 +64,160,2427200,1,4 +144,112,2427424,2,0,B|272:80,2,100,0|2|0 +192,192,2428096,2,0,B|208:320,1,100,8|0 +307,291,2428544,2,0,B|320:192,1,100,2|0 +256,96,2428991,5,0 +208,176,2429215,1,8 +304,176,2429439,1,0 +256,64,2429663,1,0 +176,176,2429887,1,4 +336,176,2430111,1,0 +388,272,2430335,1,0 +256,304,2430559,1,0 +128,272,2430783,6,0,B|128:144|48:64,1,224.999993294478,4|0 +168,88,2431678,2,0,B|252:100|312:40,1,150.000005722046,8|0 +396,40,2432350,1,8 +452,108,2432574,1,0 +416,192,2432797,1,0 +328,176,2433021,1,8 +320,88,2433245,1,0 +416,192,2433469,6,0,B|467:195|491:221,1,74.9999977648259,8|0 +420,280,2433917,2,0,B|384:364|288:344,1,150.000005722046,0|4 +324,260,2434589,1,0 +324,260,2434812,2,0,B|332:180|402:139,2,149.999995529652,0|8|0 +300,348,2435932,1,8 +220,200,2436156,1,0 +400,140,2436380,6,0,B|352:140|320:108,2,74.9999977648259,8|8|0 +348,32,2437051,2,0,B|348:120|260:144,1,150.000005722046,8|0 +260,232,2437723,1,0 +260,60,2437947,2,0,B|163:69|157:170,1,149.999995529652,4|0 +156,244,2438618,1,0 +256,336,2438842,2,0,B|352:336|360:248,2,150.000005722046,8|0|0 +256,336,2439962,1,8 +268,248,2440186,6,0,B|168:124,1,150.000005722046,0|8 +120,204,2440857,1,0 +80,124,2441081,2,0,B|168:112|196:40,2,150.000005722046,0|4|0 +120,204,2442200,1,8 +168,280,2442424,2,0,B|216:328|280:344|360:328|408:280|424:216,2,299.999991059304,8|0|0 +224,208,2444439,5,0 +352,128,2444663,1,0 +224,64,2444887,1,0 +128,80,2445111,6,2,B|80:112|96:192,1,100,6|2 +48,256,2445559,1,0 +96,336,2445783,2,0,B|160:320|224:368,1,100,0|2 +288,352,2446230,1,0 +368,320,2446454,1,2 +288,352,2446678,2,2,B|240:256|288:160,1,200 +288,64,2447350,2,0,B|176:48,1,100 +352,128,2447797,2,2,B|464:144,1,100,2|0 +400,224,2448245,5,2 +400,320,2448469,2,2,B|304:272|192:320,1,200,6|0 +128,352,2449141,2,2,B|144:288|128:240,1,100,2|0 +80,64,2449589,2,2,B|64:128|80:176,1,100,2|2 +176,192,2450036,1,0 +176,192,2450260,2,2,B|64:224|48:352,1,200 +128,256,2450932,6,0,B|240:304,1,100 +288,368,2451380,1,2 +384,320,2451603,2,0,B|475:280,1,100 +464,176,2452051,6,2,B|352:176|288:48,1,200,6|0 +302,73,2452723,1,2 +180,56,2452947,1,0 +152,180,2453171,1,2 +276,204,2453394,1,0 +152,180,2453618,1,2 +32,256,2453842,2,2,B|80:368|224:352,1,200,0|2 +256,288,2454514,1,0 +320,352,2454738,6,2,B|384:336|400:256,2,100,0|2|2 +256,288,2455409,1,0 +320,224,2455633,2,2,B|384:112|512:144,1,200,4|0 +400,128,2456305,1,0 +400,128,2456529,2,2,B|272:112|256:0,1,200,2|0 +176,32,2457200,2,2,B|192:160|64:208,1,200,0|2 +64,288,2457872,1,0 +64,288,2458096,6,0,B|128:288|176:336,1,100 +352,348,2458544,1,4 +240,352,2458768,1,0 +288,272,2458991,1,0 +304,256,2459103,1,0 +320,240,2459215,1,0 +336,224,2459327,1,0 +352,208,2459439,1,4 +256,48,2459887,6,0,B|176:128,2,100,0|2|2 +320,112,2460559,2,2,B|240:192,1,100 +384,176,2461006,2,2,B|240:320,1,200 +160,336,2461678,1,0 +80,288,2461902,1,0 +64,192,2462126,6,0,B|48:144|64:80,1,100,2|0 +128,144,2462574,2,2,B|144:192|128:256,1,100,2|0 +208,208,2463021,2,0,B|224:80|352:64,1,200,2|0 +416,96,2463693,1,0 +384,176,2463917,2,0,B|432:384,2,200,2|2|0 +304,224,2465036,1,0 +304,224,2465260,6,0,B|336:336,1,100 +192,240,2465708,2,0,B|160:128,1,100 +32,112,2466156,1,0 +144,32,2466380,1,0 +144,32,2466603,6,2,B|192:64|240:48,2,100,6|0|0 +144,128,2467275,2,2,B|224:192|208:320,1,200 +304,304,2467947,1,0 +304,304,2468171,2,2,B|289:188|369:124,1,200 +416,48,2468842,5,4 +256,32,2469066,1,0 +96,48,2469290,1,4 +192,128,2469514,1,0 +320,128,2469738,1,0 +256,240,2469962,1,0 +256,240,2470409,1,0 +256,192,2470633,12,0,2472872 +256,64,2487424,5,0 +256,64,2487648,1,0 +432,192,2488096,5,4 +368,256,2488320,2,0,B|368:368,2,100,0|0|8 +336,176,2488991,2,0,B|240:112,2,100,0|0|8 +256,224,2489663,2,0,B|160:160,2,100,0|0|8 +176,272,2490335,2,0,B|112:272|80:336,2,100,0|0|8 +336,272,2491006,2,0,B|400:272|432:336,2,100,0|0|8 +384,192,2491678,5,4 +416,112,2491902,1,0 +384,32,2492126,1,0 +256,32,2492350,5,8 +256,128,2492574,1,0 +256,224,2492797,1,0 +128,192,2493021,5,8 +96,112,2493245,1,0 +128,32,2493469,1,0 +256,32,2493693,6,0,B|256:160,3,100,8|0|0|8 +352,128,2494589,1,0 +432,176,2494812,2,0,B|384:208|400:288,1,100,8|0 +448,336,2495260,6,0,B|456:296|440:264|380:264|380:264|296:264,1,200,4|8 +124,256,2495932,2,0,B|116:216|132:184|192:184|192:184|276:184,1,200,0|8 +416,176,2496603,2,0,B|424:136|408:104|348:104|348:104|264:104,1,200,0|8 +164,104,2497275,2,0,B|100:24,2,100,0|0|8 +100,184,2497947,2,0,B|4:104,2,100,0|0|8 +256,192,2498618,1,0 +256,192,2498842,12,4,2500633 +384,48,2500857,6,0,B|368:112|384:160,1,100 +336,272,2501305,1,0 +336,272,2501529,2,0,B|272:64,1,200,4|0 +176,64,2502200,2,0,B|64:80,1,100,0|4 +64,176,2502648,6,0,B|32:304,1,100,8|0 +144,192,2503096,2,0,B|112:304,1,100,0|8 +224,208,2503544,2,0,B|192:320,1,100,0|8 +384,240,2503991,6,0,B|416:112,1,100 +304,224,2504439,2,0,B|336:112,1,100,8|0 +224,208,2504887,2,0,B|256:95,1,100,0|8 +176,48,2505335,5,0 +80,80,2505559,1,8 +48,176,2505783,1,0 +64,272,2506006,5,4 +256,352,2506454,1,0 +448,272,2506902,1,4 +352,96,2507350,1,0 +160,96,2507797,1,4 +256,272,2508245,2,0,B|292:185|228:158|260:74,2,200,0|4|0 +256,192,2509589,12,4,2513171 +336,48,2526380,5,0 +304,80,2526603,1,0 +272,112,2526827,1,0 +240,144,2527051,1,0 +208,176,2527275,1,0 +128,240,2527499,5,6 +48,304,2527723,2,2,B|144:336,1,100,2|0 +304,320,2528171,2,2,B|208:288,1,100,2|0 +304,224,2528618,1,2 +336,144,2528842,1,0 +304,224,2529066,2,2,B|496:288,1,200 +464,192,2529738,6,0,B|432:144|464:80,1,100 +256,64,2530186,2,2,B|368:80,1,100 +176,32,2530633,1,0 +176,32,2530857,2,2,B|176:160|16:192,1,200,6|0 +32,352,2531529,6,2,B|80:240,1,100,2|0 +208,304,2531977,1,2 +160,208,2532200,1,0 +71,261,2532424,1,0 +208,304,2532648,2,2,B|272:304|272:304|320:296|320:296|408:296,1,200,2|2 +448,204,2533320,6,0,B|320:204,1,100 +256,56,2533768,2,0,B|300:160,1,100,2|0 +220,216,2534215,1,0 +220,216,2534439,2,2,B|132:216|132:216|84:224|84:224|20:224,1,200,6|0 +80,308,2535111,1,2 +80,308,2535335,1,0 +80,308,2535559,2,2,B|132:276|148:216|120:164|72:152,1,200,2|0 +71,152,2536230,2,2,B|84:104|140:76|204:92|228:144,1,200,2|0 +380,136,2536902,5,0 +228,144,2537126,1,0 +308,208,2537350,1,2 +404,304,2537574,2,0,B|280:332,1,100,0|2 +116,256,2538021,2,2,B|192:232|192:232|248:232|248:232|324:204,1,200,6|0 +224,156,2538693,1,0 +224,156,2538917,2,2,B|300:132|300:132|356:132|356:132|432:104,1,200,6|0 +352,36,2539589,6,0,B|256:28|256:28|152:40,2,200 +312,164,2540708,1,0 +176,128,2540932,1,4 +56,156,2541156,5,0 +88,296,2541380,1,4 +220,204,2541603,1,0 +220,204,2541827,1,4 +420,224,2542275,5,0 +420,224,2542499,2,2,B|408:84,1,100,2|0 +328,68,2542947,1,2 +228,72,2543171,1,0 +328,68,2543394,2,2,B|344:272,1,200 +264,332,2544066,2,0,B|208:340|160:308,1,100 +72,336,2544514,6,2,B|192:316|180:184,1,200,2|0 +80,208,2545186,2,2,B|68:86|188:66,1,200,6|0 +284,48,2545857,2,0,B|384:40,2,100,0|2|0 +304,148,2546529,1,2 +208,116,2546753,1,0 +176,212,2546977,6,2,B|-8:316,1,200,2|0 +100,336,2547648,2,2,B|284:232,1,200,2|0 +376,248,2548320,2,0,B|436:248|460:200,2,100 +384,148,2548991,5,6 +364,48,2549215,1,0 +264,56,2549439,1,2 +192,124,2549663,2,2,B|144:228|208:320,1,200,0|2 +315,314,2550335,1,0 +315,314,2550559,2,2,B|376:228|328:124,1,200 +396,52,2551230,6,0,B|508:108,1,100 +328,124,2551678,2,0,B|240:80,1,100 +96,132,2552126,2,0,B|208:188,1,100 +256,292,2552574,1,4 +256,292,2552797,1,0 +256,192,2553021,12,4,2555708 +357,168,2579508,5,4 +232,96,2579732,1,2 +152,234,2579956,1,0 +280,300,2580179,1,0 +280,300,2580851,5,4 +152,234,2581075,1,2 +232,96,2581299,1,0 +357,168,2581523,1,0 +256,192,2582194,5,2 +256,192,2582418,1,0 +256,192,2582642,1,2 +208,364,2583090,6,0,B|224:378|256:394|288:378|304:362,1,100,4|0 +256,296,2583538,1,2 +200,232,2583762,2,0,B|163:179|192:112|255:92|304:104|346:153|336:200|312:232|312:232,1,300,0|4 +368,301,2584657,2,0,B|411:263|413:197,1,100,0|2 +405,123,2585105,1,0 +144,301,2585777,2,0,B|101:263|99:197,1,100,4|0 +107,123,2586224,1,4 +256,192,2586672,5,0 +256,192,2586784,1,0 +256,192,2586896,1,0 +256,192,2587120,1,0 +256,280,2587344,1,0 +451,140,2588015,6,0,B|465:124|481:92|465:60|449:44,1,100 +362,48,2588463,2,0,B|349:59|333:91|349:123|363:139,1,100 +336,328,2589359,1,0 +256,368,2589582,1,0 +176,328,2589806,1,0 +148,140,2590254,2,0,B|162:124|178:92|162:60|146:44,1,100 +59,48,2590702,2,0,B|46:59|30:91|46:123|60:139,1,100 +209,344,2591597,6,0,B|255:362|255:362|302:344,1,100 +301,243,2592045,2,0,B|254:225|254:225|207:244,1,100 +332,76,2592941,5,0 +256,20,2593165,1,0 +180,76,2593388,1,0 +212,156,2593612,1,0 +300,156,2593836,1,0 +300,156,2593948,1,0 +300,156,2594060,1,0 +376,204,2594284,5,0 +468,196,2594508,1,0 +504,276,2594732,1,0 +440,344,2594956,1,0 +360,296,2595179,1,0 +304,373,2595403,2,0,B|288:387|256:403|224:387|208:371,1,100,0|0 +152,296,2595851,1,0 +72,344,2596075,1,0 +8,276,2596299,1,0 +44,196,2596523,1,0 +136,204,2596747,1,0 +176,124,2596971,5,0 +208,92,2597082,1,0 +252,76,2597194,1,0 +296,92,2597306,1,0 +324,124,2597418,1,0 +256,184,2597642,1,0 +340,240,2597866,2,0,B|368:280|368:280|420:296|420:296,2,100 +256,284,2598538,2,0,B|256:388|256:388,2,100 +172,240,2599209,2,0,B|144:280|144:280|92:296|92:296,2,100 +256,180,2599881,1,0 +360,256,2600105,5,0 +360,256,2600217,1,0 +360,256,2600329,1,0 +360,256,2600441,1,0 +360,256,2600553,1,0 +360,256,2600665,1,0 +360,256,2600777,1,0 +360,256,2600888,1,0 +360,257,2601000,6,0,B|347:343|241:378,2,150,4|0|0 +400,240,2601784,1,0 +448,226,2601896,1,0 +277,64,2602344,1,0 +229,78,2602456,1,0 +181,92,2602568,1,0 +157,188,2602791,6,0,B|71:175|36:69,2,150 +145,237,2603575,1,0 +127,283,2603687,1,0 +284,159,2604135,1,0 +302,113,2604247,1,0 +314,65,2604359,1,0 +397,119,2604582,6,0,B|420:34|505:13,2,150 +444,136,2605366,1,0 +491,152,2605478,1,0 +346,290,2605926,1,0 +299,272,2606038,1,0 +252,252,2606150,1,0 +221,156,2606374,6,0,B|246:79|180:5,2,150 +179,163,2607157,1,0 +129,170,2607269,1,0 +17,336,2607717,1,0 +66,331,2607829,1,0 +115,323,2607941,1,0 +196,265,2608165,6,0,B|289:271|311:373,2,150 +205,215,2608948,1,0 +214,165,2609060,1,0 +401,233,2609508,1,0 +413,184,2609620,1,0 +426,135,2609732,1,0 +415,36,2609956,6,0,B|290:22|248:130,2,200 +216,8,2611299,1,0 +35,94,2611747,5,0 +58,137,2611859,1,0 +78,182,2611971,2,0,B|141:186|158:255,1,100 +99,323,2612418,1,0 +183,376,2612642,1,0 +183,376,2612866,1,0 +249,301,2613090,6,0,B|388:315,1,100 +429,251,2613538,1,0 +429,251,2613762,1,0 +375,167,2613985,1,0 +388,67,2614209,1,0 +196,65,2614433,6,0,B|171:20,7,50,0|0|0|0|0|0|0|0 +218,111,2615329,2,0,B|163:231|296:295,1,200,4|0 +347,216,2616000,1,0 +438,257,2616224,1,0 +414,354,2616448,5,0 +414,354,2616560,1,0 +414,354,2616672,2,0,B|175:368,1,200 +214,365,2617232,1,0 +214,365,2617344,1,0 +118,336,2617568,1,0 +161,246,2617791,1,0 +80,186,2618015,2,0,B|68:67|191:31,1,200 +180,34,2618575,1,0 +180,34,2618687,1,0 +230,121,2618911,5,0 +230,121,2619023,1,0 +230,121,2619135,2,0,B|363:104,1,100 +321,208,2619582,1,0 +321,208,2619694,1,0 +321,208,2619806,2,0,B|454:191,1,100 +482,274,2620254,6,0,B|281:299,1,200 +283,299,2620814,1,0 +283,299,2620926,1,0 +208,365,2621150,1,0 +122,312,2621374,1,0 +195,244,2621597,1,0 +195,244,2621709,1,0 +195,244,2621821,2,0,B|209:139,1,100 +148,63,2622269,1,0 +75,131,2622493,5,0 +75,131,2622605,1,0 +75,131,2622717,2,0,B|61:236,1,100 +87,327,2623165,1,0 +175,372,2623388,2,0,B|301:374|320:255,1,200 +409,218,2624060,1,0 +409,218,2624284,5,0 +103,218,2624732,1,0 +409,218,2625179,1,0 +103,218,2625627,1,0 +256,48,2626075,5,0 +256,48,2626187,1,0 +256,48,2626299,2,0,B|281:167,1,100 +186,191,2626747,1,0 +234,278,2626971,2,0,B|218:342|129:338,1,100 +88,268,2627418,1,0 +88,268,2627642,1,0 +43,178,2627866,5,0 +43,178,2627978,1,0 +43,178,2628090,2,0,B|66:72,1,100 +179,45,2628538,2,0,B|156:151,1,100 +240,255,2628985,2,0,B|263:149,1,100 +420,100,2629433,1,0 +420,100,2629545,1,0 +420,100,2629657,1,4 +340,310,2630105,6,0,B|316:352|256:374|204:357|173:311,1,200,0|0 +172,74,2631000,2,0,B|196:32|256:10|308:27|339:73,1,200,0|0 +339,73,2631560,1,0 +339,73,2631672,1,0 +43,206,2632120,6,0,B|66:312,1,100 +179,339,2632568,2,0,B|156:233,1,100 +240,129,2633015,2,0,B|263:235,1,100,0|4 +398,343,2633687,2,0,B|433:355|475:325|475:280|454:238|395:234|371:270,1,200 +393,60,2634582,2,0,B|357:47|315:77|315:122|336:164|395:168|419:132,1,200 +414,138,2635142,1,0 +414,138,2635254,1,0 +242,86,2635702,6,0,B|220:38|220:38|166:28|166:28,1,100 +119,101,2636150,1,0 +36,64,2636374,2,0,B|-24:160|36:252,1,200,0|4 +336,356,2637269,2,0,B|312:312|256:300|256:300|200:312|176:356,1,200 +476,251,2638165,2,0,B|536:160|476:64,1,200 +476,64,2638724,1,0 +476,64,2638836,1,0 +256,116,2639284,5,0 +328,172,2639508,1,0 +300,256,2639732,1,0 +212,256,2639956,1,0 +184,172,2640179,1,0 +108,128,2640403,6,0,B|80:64|80:64,2,50,0|0|0 +176,296,2640851,2,0,B|148:360|148:360,2,50 +256,96,2641299,2,0,B|256:48|256:48,2,50 +336,296,2641747,2,0,B|364:360|364:360,2,50 +404,128,2642194,2,0,B|432:64|432:64,2,50 +296,208,2642642,22,0,B|288:232|256:240|224:232|216:208,1,100 +256,120,2643090,1,0 +140,268,2643314,1,0 +172,296,2643426,1,0 +212,316,2643538,1,0 +256,324,2643650,1,0 +300,316,2643762,1,0 +340,296,2643874,1,0 +372,268,2643985,1,4 +152,152,2644433,6,0,B|130:104|130:104|76:94|76:94,1,100 +256,100,2644881,2,0,B|256:-8,1,100 +360,152,2645329,2,0,B|382:104|382:104|436:94|436:94,1,100 +209,344,2646224,6,0,B|255:362|255:362|302:344,1,100 +303,243,2646672,2,0,B|256:225|256:225|209:244,1,100 +40,50,2647344,22,0,B|61:104|116:123|179:104|196:45,1,200 +296,160,2648015,2,0,B|288:136|256:128|224:136|216:160,1,100 +317,50,2648463,2,0,B|333:104|396:123|451:104|472:50,1,200,0|0 +296,160,2649135,2,0,B|328:216|328:216|280:232|257:262|257:262|232:232|185:214|185:214|222:152,1,300 +304,348,2650030,6,0,B|256:376|204:348,1,100 +424,192,2650478,1,0 +257,53,2650702,1,0 +88,192,2650926,1,0 +88,192,2651038,1,0 +88,192,2651150,1,4 +297,350,2651597,6,0,B|297:302|297:302|337:262,1,100 +217,350,2652045,2,0,B|217:302|217:302|177:262,1,100 +417,115,2652717,22,0,B|367:16|260:-22|138:16|95:118,1,400,0|0 +172,238,2653836,2,0,B|198:283|257:300|314:282|339:237,1,200,0|0 +256,152,2654508,1,0 +256,192,2654732,12,0,2656299 +114,110,2656971,6,0,B|231:63,1,100 +398,274,2657418,2,0,B|281:321,1,100 +257,327,2657754,1,0 +207,329,2657866,1,0 +160,311,2657978,1,0 +136,266,2658090,1,0 +143,216,2658202,1,0 +179,181,2658314,2,0,B|280:89|156:-1,1,200,4|0 +376,255,2658985,6,0,B|367:325|432:349,1,100 +450,247,2659433,2,0,B|459:177|394:153,1,100 +312,172,2659881,2,0,B|259:272|112:233,1,200 +243,9,2660553,6,0,B|234:79|299:103,1,100 +367,44,2661000,1,0 +451,98,2661224,2,0,B|460:168|395:192,1,100 +454,275,2661672,2,0,B|225:269,1,200 +122,144,2662344,6,0,B|222:146,1,100 +220,311,2662791,2,0,B|420:317,1,200 +291,72,2663463,2,0,B|92:67,1,200 +96,233,2664135,5,0 +69,321,2664359,1,0 +155,346,2664582,1,0 +182,261,2664806,1,0 +96,234,2665030,1,0 +182,262,2665254,2,0,B|398:231,1,200 +506,369,2665926,6,0,B|497:299|449:257,1,100 +500,182,2666374,1,0 +427,113,2666597,2,0,B|436:43|492:10,1,100 +372,3,2667045,2,0,B|267:26|234:157,1,200 +385,212,2667717,6,0,B|381:273|324:303,1,100 +399,33,2668165,2,0,B|294:56|261:187,1,200 +79,197,2668836,2,0,B|184:174|217:43,1,200 +302,18,2669508,5,0 +376,85,2669732,2,0,B|427:96|465:165,2,100 +305,156,2670403,2,0,B|347:255|469:286,1,200 +231,107,2671075,6,0,B|145:108|111:163,2,100 +248,8,2671747,2,0,B|132:2|44:73,1,200 +22,95,2672306,1,0 +11,143,2672418,1,0 +35,186,2672530,1,0 +84,195,2672642,6,0,B|148:201|156:271,1,100,4|0 +252,240,2673090,1,0 +235,338,2673314,2,0,B|358:357|418:242,1,200 +175,277,2673985,6,0,B|110:267|73:328,1,100 +0,256,2674433,1,0 +82,199,2674657,2,0,B|47:88|92:-9,1,200 +216,125,2675329,6,0,B|294:191,1,100 +338,100,2675777,2,0,B|494:232,1,200 +463,325,2676448,1,0 +463,325,2676672,2,0,B|422:365|353:347,1,100 +275,25,2677568,5,0 +275,25,2677791,1,0 +275,25,2678015,2,0,B|303:70|297:135,1,100 +44,299,2678911,6,0,B|108:297|155:246,1,100 +47,322,2679359,2,0,B|111:320|158:269,1,100 +237,272,2679806,5,0 +237,272,2679918,1,0 +237,272,2680030,2,0,B|352:306,1,100 +370,207,2680478,1,0 +444,273,2680702,2,0,B|493:237|487:181,2,100 +425,371,2681374,6,0,B|214:354,1,200 +133,262,2682045,2,0,B|332:278,1,200 +358,196,2682717,2,0,B|252:187,1,100 +208,183,2683053,1,0 +158,178,2683165,1,0 +108,173,2683277,1,0 +58,166,2683388,2,0,B|20:124|41:55,1,100 +108,2,2683836,2,0,B|146:44|125:113,1,100 +222,57,2684284,6,0,B|433:40,1,200,0|0 +492,111,2684956,2,0,B|293:127,1,200 +235,209,2685627,2,0,B|341:200,1,100 +263,129,2686075,1,0 +213,134,2686187,1,0 +163,141,2686299,2,0,B|125:183|146:252,1,100 +256,30,2697717,5,0 +110,125,2697941,1,0 +133,290,2698165,1,0 +299,333,2698388,1,0 +421,211,2698612,1,0 +342,54,2698836,5,0 +169,54,2699060,1,0 +90,211,2699284,1,0 +212,333,2699508,1,0 +378,290,2699732,1,0 +401,125,2699956,1,0 +256,30,2700179,1,0 +256,192,2700403,5,0 +256,192,2700515,1,0 +256,192,2700627,1,0 +256,192,2700739,1,0 +256,192,2700851,1,0 +256,192,2700963,1,0 +256,192,2701075,1,0 +256,192,2701187,1,0 +256,192,2701299,1,4 +326,324,2701635,6,0,B|306:358|256:377|213:363|187:324,1,164.999995082617,0|0 +60,280,2702194,1,0 +76,148,2702418,2,0,B|88:92|88:92|132:52,1,109.999996721745 +209,147,2702866,2,0,B|219:123|254:115|289:122|302:146,1,109.999996721745 +256,188,2703202,1,0 +256,240,2703314,1,0 +384,56,2703538,2,0,B|424:92|424:92|436:148,1,109.999996721745 +326,311,2703985,2,0,B|306:345|256:364|213:350|187:311,2,164.999995082617 +256,192,2704881,5,0 +256,144,2704993,1,0 +256,96,2705105,1,0 +130,80,2705329,2,0,B|119:56|84:48|50:55|37:79,1,109.999996721745 +209,259,2705777,2,0,B|219:282|254:290|289:284|302:260,1,109.999996721745 +475,79,2706224,2,0,B|462:55|427:48|392:56|382:80,1,109.999996721745 +408,296,2706672,6,0,B|464:264,2,54.9999983608723 +300,336,2707120,2,0,B|286:360|256:366|256:366|225:360|212:336,1,109.999996721745 +192,228,2707568,2,0,B|211:192|257:182|257:182|303:192|322:228,2,164.999995082617 +104,296,2708463,2,0,B|48:264,2,54.9999983608723 +136,108,2708911,5,0 +256,32,2709135,1,0 +376,108,2709359,1,0 +336,260,2709582,1,0 +176,260,2709806,1,0 +256,152,2710030,1,0 +256,152,2710254,5,0 +404,348,2710702,1,0 +108,348,2711150,1,0 +175,131,2711597,6,0,B|192:71|257:48|320:72|338:134,1,219.999993443489,0|0 +256,156,2712157,2,0,B|256:232,1,54.9999983608723 +356,280,2712493,2,0,B|372:332|372:332|436:360|436:360,2,109.999996721745 +256,364,2713165,1,0 +156,280,2713388,2,0,B|140:332|140:332|76:360|76:360,2,109.999996721745 +156,280,2713948,1,0 +156,280,2714060,1,0 +209,160,2714284,2,0,B|219:136|254:128|289:135|302:159,1,109.999996721745 +95,106,2714732,5,0 +124,65,2714844,1,0 +161,33,2714956,1,0 +206,15,2715068,1,0 +256,8,2715179,1,0 +306,15,2715291,1,0 +351,33,2715403,1,0 +388,65,2715515,1,0 +417,106,2715627,1,4 +340,310,2716075,6,0,B|316:352|256:374|204:357|173:311,1,200,0|0 +340,129,2716971,2,0,B|316:87|256:65|204:82|173:128,1,200,0|0 +256,176,2717530,2,0,B|256:232|256:232,1,50 +400,336,2718090,6,0,B|440:224,1,100 +256,176,2718538,2,0,B|256:276,1,100 +112,336,2718985,2,0,B|72:224,1,100,0|4 +176,88,2719657,6,0,B|200:44|256:32|256:32|312:44|336:88,1,200 +336,296,2720553,2,0,B|312:340|256:352|256:352|200:340|176:296,1,200 +256,252,2721112,2,0,B|256:184|256:184,1,50 +160,48,2721672,5,0 +216,120,2721896,2,0,B|224:96|256:88|288:96|296:120,1,100 +352,48,2722344,1,0 +256,204,2722791,5,4 +160,356,2723239,1,0 +352,356,2723687,1,0 +340,157,2724135,2,0,B|316:115|256:93|204:110|173:156,1,200,0|0 +256,204,2724694,2,0,B|256:260|256:260,1,50 +416,92,2725254,5,0 +348,28,2725478,1,0 +256,8,2725702,1,0 +164,28,2725926,1,0 +96,92,2726150,1,0 +160,164,2726374,5,0 +144,212,2726485,1,0 +104,244,2726597,1,0 +208,316,2726821,2,0,B|190:336|190:336|180:360,2,50 +304,316,2727269,2,0,B|322:336|322:336|332:360,2,50 +408,244,2727717,1,0 +368,212,2727829,1,0 +352,164,2727941,1,0 +184,80,2728165,5,0 +80,104,2728388,1,0 +328,80,2728612,5,0 +432,104,2728836,1,0 +256,32,2729060,5,0 +256,104,2729284,1,0 +270,190,2729508,1,0 +270,190,2729620,1,0 +270,190,2729732,1,0 +270,190,2729844,1,0 +270,190,2729956,6,0,B|271:312|126:334,1,200,4|0 +344,248,2730627,2,0,B|402:270|419:337,2,100 +409,169,2731299,1,0 +310,151,2731523,2,0,B|325:32|457:27,1,200 +297,226,2732194,6,0,B|286:279|226:297,2,100 +204,186,2732866,1,0 +131,117,2733090,1,0 +208,53,2733314,2,0,B|432:48,1,200 +226,288,2733985,6,0,B|160:280|141:204,1,100 +276,358,2734433,2,0,B|393:349|414:209,1,200 +236,26,2735105,2,0,B|119:35|98:175,1,200 +166,229,2735777,5,0 +84,285,2736000,2,0,B|65:344|-6:353,2,100 +48,190,2736672,1,0 +118,118,2736896,1,0 +118,118,2737008,1,0 +118,118,2737120,2,0,B|326:103,1,200,4|0 +455,238,2737791,6,0,B|393:233|379:150,2,100 +476,139,2738463,1,0 +458,301,2738687,2,0,B|338:294|324:163,1,200 +476,139,2739359,1,0 +476,139,2739582,1,0 +416,356,2739806,6,0,B|295:340|272:206,1,200 +96,28,2740478,2,0,B|216:43|240:178,1,200 +145,200,2741150,1,0 +218,268,2741374,2,0,B|330:261,2,100 +169,355,2742045,6,0,B|389:363,1,200 +419,366,2742605,1,0 +468,367,2742717,1,0 +500,272,2742941,1,0 +448,186,2743165,1,0 +397,184,2743277,1,0 +347,181,2743388,1,0 +369,83,2743612,5,0 +318,82,2743724,1,0 +268,78,2743836,1,0 +217,77,2743948,1,0 +166,75,2744060,1,0 +116,71,2744172,1,0 +66,68,2744284,6,0,B|22:166|85:277,1,200,4|0 +174,237,2744956,1,0 +152,334,2745179,1,0 +152,334,2745403,2,0,B|259:358,1,100 +316,280,2745851,2,0,B|435:312|488:212,1,200 +194,111,2746523,5,0 +274,50,2746747,1,0 +194,111,2746971,1,0 +194,111,2747194,1,0 +261,185,2747418,1,0 +206,269,2747642,2,0,B|236:385|393:359,1,200 +438,298,2748314,6,0,B|440:198,1,100 +330,214,2748762,2,0,B|336:14,1,200 +209,285,2749433,2,0,B|214:86,1,200 +445,24,2750105,5,0 +345,30,2750329,1,0 +245,36,2750553,1,0 +145,41,2750777,1,0 +150,140,2751000,1,0 +50,153,2751224,2,0,B|21:275|129:337,1,200 +444,267,2751896,5,0 +345,254,2752120,1,0 +345,254,2752344,1,0 +444,267,2752568,1,0 +345,254,2752791,1,0 +285,335,2753015,2,0,B|161:325|165:199,1,200 +80,150,2753687,6,0,B|40:183|33:247,1,100 +139,102,2754135,2,0,B|234:49|359:83,1,200 +373,282,2754806,2,0,B|278:335|153:301,1,200 +147,211,2755478,5,0 +78,284,2755702,2,0,B|4:294|-11:364,2,100 +20,201,2756374,2,0,B|62:102|17:-2,1,200 +207,276,2757045,6,0,B|272:283|323:217,2,100 +156,362,2757717,2,0,B|249:393|381:350,1,200 +401,346,2758277,1,0 +443,319,2758388,1,0 +467,275,2758500,1,4 +480,228,2758612,5,4 +304,57,2759060,2,0,B|335:101|310:159|256:176|213:166|177:124|184:84|207:56|207:56,1,259.999995350838,0|0 +32,228,2759956,1,0 +298,356,2760403,2,0,B|312:296|312:296|256:256|256:256|199:296|199:296|215:357|215:357,1,259.999995350838 +368,112,2761299,5,0 +256,48,2761523,1,0 +144,112,2761747,1,0 +223,233,2761971,2,0,B|232:217|256:211|282:220|290:232,1,74.9999977648259 +256,304,2762194,1,0 +474,73,2762642,6,0,B|430:90|431:171|489:190|489:190|430:215|427:293|489:319,1,299.999991059304 +37,311,2763538,6,0,B|80:288|82:215|23:190|23:190|81:171|82:90|38:73,1,299.999991059304 +256,296,2764433,5,0 +256,24,2764881,1,0 +308,169,2765105,1,0 +184,88,2765329,1,0 +336,88,2765553,1,0 +336,88,2765665,1,0 +336,88,2765777,1,0 +212,168,2766000,1,0 +80,224,2766224,5,0 +144,328,2766448,1,0 +256,368,2766672,1,0 +368,328,2766896,1,0 +432,224,2767120,1,0 +298,192,2767344,2,0,B|302:215|288:248|259:253|233:252|210:230|206:209|215:190|215:190,1,149.999995529652,0|0 +192,82,2767791,2,0,B|256:42|256:42|320:82,1,149.999995529652 +397,162,2768239,6,0,B|465:191|465:191|484:274,1,149.999995529652 +440,362,2768687,1,0 +344,312,2768911,1,0 +308,348,2769023,1,0 +256,360,2769135,1,0 +204,348,2769247,1,0 +168,312,2769359,1,0 +72,364,2769582,1,0 +30,265,2769807,2,0,B|47:191|47:191|115:162,1,149.999995529652 +96,60,2770254,5,0 +196,96,2770478,2,0,B|205:120|244:118|256:89|256:89|265:118|306:118|315:97,1,149.999995529652 +420,60,2770926,1,0 +404,164,2771150,1,0 +318,240,2771374,2,0,B|299:208|254:191|215:204|191:239,1,149.999995529652,0|0 +208,340,2771821,1,0 +256,316,2771933,1,0 +304,340,2772045,1,0 +388,168,2772269,5,0 +356,128,2772381,1,0 +308,100,2772493,1,0 +256,88,2772605,1,0 +204,100,2772717,1,0 +156,128,2772829,1,0 +124,168,2772941,1,4 +256,296,2773165,5,0 +256,296,2773277,1,0 +256,296,2773388,1,0 +120,360,2773612,1,0 +28,240,2773836,1,0 +112,116,2774060,1,0 +256,152,2774284,1,0 +256,296,2774508,5,0 +256,296,2774620,1,0 +256,296,2774731,1,0 +392,360,2774955,1,0 +484,240,2775179,1,0 +400,116,2775403,1,0 +320,16,2775627,6,0,B|301:48|256:65|217:52|193:17,1,149.999995529652,0|0 +112,120,2776075,1,0 +204,240,2776299,1,0 +256,220,2776411,1,0 +304,240,2776523,1,4 +196,344,2776747,2,0,B|213:376|255:385|255:385|297:376|315:344,1,149.999995529652 +436,252,2777194,5,0 +396,108,2777418,1,0 +256,56,2777642,1,0 +116,108,2777866,1,0 +76,252,2778090,1,0 +76,252,2778202,1,0 +76,252,2778314,1,0 +208,348,2778538,5,0 +256,208,2778762,1,0 +304,348,2778985,1,0 +180,264,2779209,1,0 +332,264,2779433,1,0 +348,124,2779657,1,0 +312,80,2779769,1,0 +256,60,2779881,1,0 +200,80,2779993,1,0 +164,124,2780105,1,4 +76,240,2780329,5,0 +256,160,2780553,1,0 +436,244,2780777,1,0 +192,368,2781000,2,0,B|211:336|256:319|295:332|319:367,2,149.999995529652 +192,192,2781672,1,0 +256,192,2781784,1,0 +320,192,2781896,1,0 +320,16,2782120,6,0,B|301:48|256:65|217:52|193:17,2,149.999995529652 +424,120,2782791,1,0 +256,192,2783015,1,0 +96,120,2783239,1,0 +256,352,2783463,1,0 +256,192,2783687,6,0,B|252:203|238:234|285:241|301:236|315:230|336:212|323:182|328:171|332:161|326:145|310:125|295:132|283:107|277:112|244:105|219:108|204:131|189:147|180:169|180:169|169:197|172:220|178:254|191:280|206:310|255:317|286:330|322:338|357:307|381:282|403:250|408:216|414:197|422:176|425:150|406:108|386:57|353:37|305:22|251:26|214:22|174:41|139:77|121:113|107:146|107:146|97:176|87:233|95:284|124:325|166:371|200:383|250:410|330:406|384:383|417:348|445:311|483:242|479:162,1,1799.99994635582 +478,147,2786485,1,0 +472,126,2786597,1,0 +466,104,2786709,1,0 +457,83,2786821,1,0 +444,64,2786933,1,0 +432,44,2787045,1,0 +415,28,2787157,1,0 +397,14,2787269,1,4 +256,80,2801597,5,4 +256,192,2801709,12,0,2804284 +366,72,2804508,5,0 +422,120,2804732,1,0 +422,192,2804956,1,0 +366,239,2805179,2,0,B|253:192|145:240,1,224.999993294478,4|0 +113,306,2806075,2,0,B|258:390|408:299,1,300 +494,192,2807418,1,0 +398,79,2807866,2,0,B|258:-6|114:78,1,299.999991059304,4|4 +256,192,2808874,12,0,2811896 +64,152,2812120,5,0 +64,232,2812344,1,4 +256,192,2812456,12,0,2814135 +424,116,2814359,5,0 +426,153,2814471,1,0 +411,187,2814582,1,0 +380,208,2814694,1,0 +342,205,2814806,1,0 +309,186,2814918,1,0 +272,177,2815030,1,0 +234,178,2815142,1,0 +199,192,2815254,1,0 +177,222,2815366,1,0 +167,258,2815478,1,0 +171,293,2815590,1,0 +195,321,2815702,1,0 +227,339,2815814,1,0 +264,339,2815926,6,0,B|306:298|375:323,1,100 +441,263,2816374,1,0 +362,201,2816597,2,0,B|347:124|383:92,2,100 +272,248,2817269,1,0 +201,177,2817493,5,0 +201,177,2817605,1,0 +201,177,2817717,2,0,B|93:194,2,100 +289,129,2818388,1,0 +312,32,2818612,5,0 +410,30,2818836,1,0 +204,32,2819060,1,0 +106,30,2819284,1,0 +29,94,2819508,6,0,B|14:141|65:193,1,100 +57,184,2819844,1,0 +57,184,2819956,1,0 +52,284,2820179,5,0 +52,284,2820291,1,0 +52,284,2820403,2,0,B|170:273,1,100 +151,274,2820739,1,0 +151,274,2820851,1,0 +232,333,2821075,5,0 +232,333,2821187,1,0 +232,333,2821299,2,0,B|298:328|314:249,1,100 +306,275,2821635,1,0 +306,275,2821747,1,0 +401,305,2821971,5,0 +401,305,2822082,1,0 +401,305,2822194,2,0,B|382:81,1,200 +349,69,2822754,1,0 +303,47,2822866,1,0 +254,60,2822978,1,0 +232,105,2823090,6,0,B|234:166|174:188,1,100 +147,220,2823426,1,0 +111,255,2823538,1,0 +204,291,2823762,5,0 +239,326,2823874,1,0 +274,361,2823985,2,0,B|398:345,1,100 +421,363,2824321,1,0 +468,379,2824433,1,0 +462,279,2824657,5,0 +437,235,2824769,1,0 +392,211,2824881,2,0,B|379:150|437:120,1,100 +471,99,2825217,1,0 +480,49,2825329,1,0 +387,10,2825553,5,0 +337,16,2825665,1,0 +288,27,2825777,2,0,B|282:137|177:182,1,200 +235,263,2826448,1,0 +134,263,2826672,2,0,B|109:321|132:372,1,100,4|0 +127,360,2827008,1,0 +127,360,2827120,1,0 +225,343,2827344,5,0 +321,367,2827568,1,0 +338,268,2827791,1,0 +338,268,2827903,1,0 +338,268,2828015,2,0,B|479:251,1,100 +373,124,2828463,6,0,B|273:135,1,100 +189,80,2828911,1,0 +149,111,2829023,1,0 +139,160,2829135,1,0 +157,206,2829247,1,0 +196,236,2829359,1,0 +245,243,2829471,1,0 +290,262,2829582,1,0 +316,304,2829694,1,0 +307,353,2829806,1,0 +263,377,2829918,1,0 +213,381,2830030,1,0 +166,361,2830142,1,0 +116,366,2830254,1,4 +48,296,2830478,5,0 +17,256,2830590,1,0 +19,206,2830702,1,0 +54,168,2830814,1,0 +100,160,2830926,1,0 +145,180,2831038,1,0 +194,188,2831150,1,0 +243,181,2831262,1,0 +285,154,2831374,1,0 +302,107,2831485,1,0 +291,58,2831597,1,0 +255,22,2831709,1,0 +205,14,2831821,1,0 +160,36,2831933,1,0 +136,80,2832045,1,0 +194,188,2832269,5,0 +226,222,2832381,1,0 +232,273,2832493,1,0 +204,314,2832605,1,0 +155,328,2832717,1,0 +101,243,2832941,1,0 +53,227,2833053,1,0 +28,183,2833165,1,0 +112,132,2833388,1,0 +130,87,2833500,1,0 +176,62,2833612,1,0 +224,71,2833724,1,0 +255,111,2833836,1,0 +340,144,2834060,5,0 +390,145,2834172,1,0 +434,121,2834284,1,0 +492,202,2834508,2,0,B|456:227|438:286|453:314,1,100 +360,348,2834956,1,0 +360,348,2835068,1,0 +360,348,2835180,1,0 +296,288,2835403,2,0,B|288:312|256:320|224:312|216:288,1,100 +152,348,2835851,1,0 +152,348,2835963,1,0 +152,348,2836075,1,0 +256,208,2836299,5,0 +170,101,2836523,2,0,B|203:58|256:44|312:56|344:104,3,200,0|0|4|0 +404,192,2838090,5,0 +384,200,2838202,1,0 +368,212,2838314,1,0 +356,228,2838426,1,0 +348,248,2838538,1,0 +344,268,2838650,1,0 +348,288,2838762,1,0 +356,308,2838874,1,0 +368,324,2838985,1,0 +384,336,2839097,1,0 +404,344,2839209,1,0 +256,196,2839657,5,0 +108,192,2839881,5,0 +128,200,2839993,1,0 +144,212,2840105,1,0 +156,228,2840217,1,0 +164,248,2840329,1,0 +168,268,2840441,1,0 +164,288,2840553,1,0 +156,308,2840665,1,0 +144,324,2840776,1,0 +128,336,2840888,1,0 +108,344,2841000,1,0 +216,124,2841448,6,0,B|224:148|256:156|288:148|296:124,1,100 +296,123,2841784,1,0 +296,123,2841896,1,0 +256,36,2842120,1,0 +216,260,2842344,6,0,B|224:236|256:228|288:236|296:260,1,100 +256,348,2842791,1,0 +256,192,2842847,12,4,2843687 +256,192,2843799,12,0,2844582 +176,104,2845030,5,0 +256,48,2845254,1,0 +336,104,2845478,1,0 +308,200,2845702,1,0 +204,200,2845926,1,0 +183,297,2846150,2,0,B|198:352|258:373|316:351|333:294,1,200,0|0 +424,344,2846821,6,0,B|448:296|448:296|496:280,1,100 +424,208,2847269,1,0 +456,112,2847493,1,0 +372,48,2847717,1,0 +293,106,2847941,2,0,B|316:139|297:184|256:197|223:189|195:157|201:126|218:105|218:105,1,200,0|0 +140,48,2848612,1,0 +56,112,2848836,1,0 +88,208,2849060,1,0 +256,352,2849508,1,0 +338,300,2849732,2,0,B|310:254|257:240|202:251|172:305,1,200 +120,216,2850403,5,0 +208,168,2850627,1,0 +256,156,2850739,1,0 +304,168,2850851,1,0 +392,216,2851075,1,0 +396,116,2851299,1,0 +304,56,2851523,1,0 +256,44,2851635,1,0 +208,56,2851747,1,4 +116,116,2851971,1,0 +120,216,2852194,5,0 +208,268,2852418,2,0,B|184:276|176:308|184:340|208:348,1,100 +303,348,2852866,2,0,B|328:340|336:308|328:276|304:268,1,100 +392,216,2853314,6,0,B|336:188|332:132|332:132|384:121|408:60,1,200 +308,32,2853985,1,0 +204,32,2854209,1,0 +111,75,2854433,2,0,B|134:120|180:132|180:132|176:188|120:216,1,200 +216,280,2855105,2,0,B|227:258|255:252|255:252|283:258|295:280,1,100 +256,192,2855385,12,0,2856224 +256,192,2856280,12,0,2857120 +333,71,2857568,6,0,B|328:134|394:164,1,100 +179,313,2858015,2,0,B|184:250|118:220,1,100 +96,197,2858351,1,0 +74,151,2858463,1,0 +87,102,2858575,1,0 +129,75,2858687,1,0 +178,79,2858799,1,0 +218,108,2858911,2,0,B|298:184|412:151,1,200,4|0 +163,154,2859582,6,0,B|192:206|164:266,2,100 +93,81,2860254,1,0 +23,153,2860478,2,0,B|-22:249|34:367,1,200 +308,324,2861150,6,0,B|210:318,2,100 +353,234,2861821,2,0,B|362:164|297:140,1,100 +247,222,2862269,2,0,B|41:209,1,200 +126,25,2862941,6,0,B|102:71|96:129,1,100 +278,101,2863388,2,0,B|293:199|248:304,1,200 +428,259,2864060,2,0,B|404:145|420:45,1,200 +243,29,2864732,6,0,B|369:47,2,100 +143,15,2865403,1,0 +107,50,2865515,1,0 +105,100,2865627,1,0 +138,137,2865739,1,0 +184,155,2865851,1,0 +216,193,2865963,1,0 +227,241,2866075,2,0,B|261:337|415:331,1,200,4|0 +359,235,2866747,2,0,B|395:120,1,100 +474,190,2867194,2,0,B|510:75,1,100 +269,128,2867642,2,0,B|202:341,1,200 +460,196,2868314,6,0,B|496:81,1,100 +255,134,2868762,2,0,B|188:347,1,200 +195,324,2869433,1,0 +256,192,2869545,12,0,2873239 +256,96,2873463,5,0 +256,96,2873575,1,0 +256,96,2873687,1,0 +256,96,2873911,1,0 +256,96,2874135,1,0 +256,288,2874359,5,0 +256,288,2874471,1,0 +256,288,2874582,1,0 +256,288,2874806,1,0 +256,288,2875030,1,0 +352,192,2875254,5,0 +352,192,2875366,1,0 +352,192,2875478,1,0 +352,192,2875702,1,0 +352,192,2875926,1,0 +160,192,2876150,5,0 +160,192,2876262,1,0 +160,192,2876374,1,0 +160,192,2876597,1,0 +160,192,2876821,1,0 +296,56,2877045,5,0 +304,56,2877157,1,0 +312,56,2877269,1,0 +216,56,2877493,1,0 +200,56,2877717,1,0 +216,328,2877941,5,0 +208,328,2878053,1,0 +200,328,2878165,1,0 +296,328,2878389,1,0 +312,328,2878613,1,0 +256,192,2878724,12,0,2880403 +392,128,2880627,5,0 +392,128,2880739,1,0 +392,128,2880851,1,0 +392,248,2881075,1,0 +392,128,2881299,1,0 +120,256,2881523,5,0 +120,256,2881635,1,0 +120,256,2881747,1,0 +120,136,2881971,1,0 +120,256,2882194,1,0 +320,328,2882418,5,0 +320,328,2882530,1,0 +320,328,2882642,1,0 +200,328,2882866,1,0 +320,328,2883090,1,0 +192,56,2883314,5,0 +192,56,2883426,1,0 +192,56,2883538,1,0 +312,56,2883762,1,0 +192,56,2883985,1,0 +256,192,2884209,5,0 +256,192,2884321,1,0 +256,192,2884433,1,0 +256,192,2884657,1,0 +256,192,2884881,1,0 +256,192,2885105,5,0 +256,192,2885217,1,0 +256,192,2885329,1,0 +256,192,2885553,1,0 +256,192,2885777,1,0 +64,344,2886224,5,0 +96,344,2886336,1,0 +128,344,2886448,1,0 +160,344,2886560,1,0 +192,344,2886672,1,0 +224,344,2886784,1,0 +256,344,2886896,1,0 +288,344,2887008,1,0 +320,344,2887120,1,0 +352,344,2887232,1,0 +384,344,2887344,1,0 +416,344,2887456,1,0 +448,344,2887568,1,0 +256,192,2887679,12,0,2889359 +176,192,2901769,6,0,B|196:288|272:340|364:360,1,240.000005722046 +332,84,2903251,1,0 +428,136,2903621,1,2 +336,192,2903991,1,0 +428,252,2904362,1,2 +336,192,2904732,6,0,B|316:288|240:340|148:360,1,240.000005722046 +180,84,2906214,1,0 +84,136,2906584,1,2 +176,192,2906954,1,0 +84,252,2907325,1,2 +176,192,2907695,6,0,B|196:96|272:44|364:24,1,240.000005722046 +332,300,2909176,1,0 +428,248,2909547,1,2 +336,192,2909917,1,0 +428,132,2910288,1,2 +336,192,2910658,6,0,B|316:96|240:44|148:24,1,240.000005722046 +180,324,2912139,1,0 +332,324,2912510,1,2 +176,192,2912880,1,0 +204,236,2913065,1,0 +256,248,2913251,1,2 +308,236,2913436,1,0 +336,192,2913621,5,0 +76,324,2914362,1,0 +236,96,2915102,1,0 +340,128,2915473,2,0,B|308:164|304:200|352:248,1,120.000002861023,2|0 +240,276,2916214,1,2 +176,192,2916584,5,0 +436,324,2917325,1,0 +276,96,2918065,1,0 +172,128,2918436,2,0,B|204:164|208:200|160:248,1,120.000002861023,2|0 +272,276,2919176,1,2 +336,192,2919547,5,0 +76,60,2920288,1,0 +236,288,2921028,1,0 +340,256,2921399,2,0,B|308:220|304:184|352:136,1,120.000002861023,2|0 +240,108,2922139,1,2 +176,192,2922510,5,0 +436,60,2923251,1,0 +316,312,2923991,1,0 +228,296,2924362,2,0,B|228:280|228:280|228:208|280:208|280:120|280:120|280:104,1,200 +196,88,2925473,1,0 +174,95,2940288,6,0,B|194:52|255:27|316:48|338:97,2,200,2|8|2 +256,144,2942139,1,0 +208,224,2942510,1,8 +304,224,2942880,1,0 +347,302,2943251,2,0,B|308:333|270:317|255:302|255:302|240:317|199:333|164:302,1,200,2|8 +88,155,2944732,5,2 +68,67,2945102,1,0 +149,28,2945473,1,8 +210,93,2945843,1,0 +181,184,2946214,2,0,B|191:220|255:244|255:244|319:220|333:183,1,200,2|8 +424,229,2947695,5,2 +444,317,2948065,1,0 +363,356,2948436,1,8 +302,291,2948806,1,0 +331,200,2949176,2,0,B|321:164|257:140|257:140|194:160|181:202,1,200,2|8 +352,88,2950658,1,2 +256,48,2951028,1,0 +152,88,2951399,1,8 +44,56,2951769,21,8 +44,56,2951954,1,0 +44,56,2952139,1,4 +160,348,2952880,1,8 +36,172,2953436,1,0 +36,172,2953621,1,0 +192,140,2953991,1,0 +320,244,2954362,1,8 +476,212,2954732,1,0 +476,212,2954917,2,0,B|424:8|112:76|168:296,1,500,0|8 +320,244,2956214,1,0 +476,212,2956584,5,0 +320,244,2956954,1,0 +192,140,2957325,1,8 +36,172,2957695,1,0 +36,172,2957880,2,0,B|88:376|400:308|344:88,1,500,0|8 +192,140,2959176,1,0 +112,276,2959547,5,0 +256,344,2959917,1,0 +400,276,2960288,1,8 +340,136,2960658,2,0,B|364:66|291:34|256:69|256:69|221:34|148:66|172:136,2,299.999991059304 +460,270,2962510,1,0 +421,325,2962695,1,0 +360,300,2962880,1,0 +256,256,2963251,1,8 +152,300,2963621,1,0 +91,325,2963806,1,0 +52,270,2963991,5,0 +352,56,2964732,1,8 +116,92,2965288,1,0 +116,92,2965473,1,0 +176,240,2965843,1,0 +336,240,2966214,1,8 +396,92,2966584,1,0 +396,92,2966769,2,0,B|492:128|532:196|532:304|448:392|348:384|260:320,1,500,0|8 +396,92,2968065,5,0 +396,92,2968251,1,0 +396,92,2968436,1,0 +336,240,2968806,1,0 +176,240,2969176,1,8 +116,92,2969547,1,0 +116,92,2969732,2,0,B|20:128|-20:196|-20:304|64:392|164:384|252:320,1,500,0|8 +256,36,2971399,5,0 +176,172,2971769,1,0 +336,172,2972139,1,8 +432,300,2972510,2,0,B|356:399|257:327|257:327|158:399|83:300,1,400 +256,236,2973621,1,8 +432,172,2973991,2,0,B|356:73|257:144|257:144|158:73|83:172,1,400 +256,236,2975102,1,8 +256,60,2975473,1,8 +256,60,2975658,1,0 +256,60,2975843,5,4 +378,372,2976584,2,0,B|348:306|260:264|172:297|136:371,1,300,8|0 +156,204,2977325,2,0,B|372:204,1,200,8|8 +296,32,2978065,2,0,B|314:70|296:101|268:115|240:115|212:101|194:70|212:32,1,200,8|0 +126,127,2978621,2,0,B|174:192|256:213|337:192|385:127,1,300 +432,304,2979547,5,8 +256,352,2979917,1,0 +80,304,2980288,1,8 +181,159,2980658,2,0,B|191:195|255:219|255:219|319:195|333:158,1,200,0|8 +127,79,2981399,2,0,B|143:14|207:-1|272:14|304:46|288:95|256:111|223:95|207:46|239:14|304:-1|368:14|384:79,1,400 +480,224,2982510,5,8 +360,322,2982880,2,0,B|325:339|290:339|256:322|221:304|204:252|221:201|256:183|290:201|308:252|290:304|256:322|221:339|187:339|152:322,1,400 +32,224,2983991,1,8 +173,146,2984362,2,0,B|181:115|213:90|254:84|254:84|281:74|303:41|281:9|257:3|229:9|201:41|227:74|254:84|254:84|297:90|324:111|338:152,1,400 +448,288,2985473,1,8 +256,368,2985843,5,8 +184,312,2986028,1,8 +208,224,2986214,1,0 +304,224,2986399,1,8 +328,312,2986584,1,8 +334,99,2986954,2,0,B|317:52|255:28|199:50|176:101,1,200,4|0 +256,136,2987510,1,0 +256,136,2987695,5,4 +256,352,2988436,1,8 +412,108,2988991,1,0 +412,194,2989176,2,0,B|362:199|313:154|308:94|344:51,1,200,8|0 +167,51,2989917,2,0,B|203:94|198:154|149:199|100:194,1,200,8|0 +100,108,2990473,1,0 +256,204,2990843,1,0 +330,254,2991028,2,0,B|320:290|256:314|256:314|192:290|178:253,1,200,0|8 +364,344,2991769,5,0 +420,140,2992139,1,0 +256,48,2992510,1,0 +92,140,2992880,1,8 +148,344,2993251,1,0 +256,48,2993991,5,0 +432,336,2994732,1,0 +80,332,2995473,1,0 +256,304,2995843,1,8 +172,152,2996214,2,0,B|136:76|172:14|227:-12|283:-12|338:14|374:76|338:152,1,400 +472,276,2997325,1,8 +311,381,2997695,2,0,B|311:325|255:307|255:307|200:288|200:233,1,200,0|8 +256,168,2998251,1,8 +311,232,2998436,2,0,B|311:288|256:307|256:307|201:325|201:381,1,200,8|8 +39,276,2999176,1,8 +39,276,2999362,1,8 +39,276,2999547,1,4 +256,192,2999639,12,8,3001769 +256,192,3002510,5,4 +468,56,3008436,5,4 +352,348,3009176,1,8 +476,172,3009732,1,0 +476,172,3009917,1,0 +320,140,3010288,1,0 +192,244,3010658,1,8 +36,212,3011028,1,0 +36,212,3011214,2,0,B|88:8|400:76|344:296,1,500,0|8 +36,212,3012880,5,0 +192,244,3013251,1,0 +320,140,3013621,1,8 +476,172,3013991,1,0 +476,172,3014176,2,0,B|424:376|112:308|168:88,1,500,0|8 +320,140,3015473,1,0 +400,276,3015843,5,0 +256,344,3016214,1,0 +112,276,3016584,1,8 +172,136,3016954,2,0,B|148:66|221:34|256:69|256:69|291:34|364:66|340:136,2,299.999991059304 +256,272,3018806,1,0 +412,300,3019176,1,0 +256,272,3019547,1,8 +100,300,3019917,1,0 +100,300,3020102,1,0 +100,300,3020288,5,0 +376,99,3021028,2,0,B|349:30|255:-8|170:25|135:103,1,300,8|0 +176,180,3021769,2,0,B|193:227|255:251|311:229|334:178,1,200,0|0 +488,264,3022510,1,8 +336,348,3022880,1,0 +256,372,3023065,1,0 +176,348,3023251,1,0 +256,52,3023991,5,8 +24,264,3024547,1,0 +24,264,3024732,1,0 +120,112,3025102,1,0 +256,228,3025473,1,8 +392,112,3025843,1,0 +336,180,3026028,2,0,B|308:136|239:99|166:123|80:208|151:317|208:352|279:356|344:292|344:245,1,500,0|8 +448,352,3027325,5,0 +255,229,3027695,1,0 +64,352,3028065,1,0 +32,128,3028436,5,8 +173,13,3028806,2,0,B|137:89|173:151|228:177|284:177|339:151|375:89|339:13,1,400 +480,128,3029917,1,8 +352,288,3030288,5,0 +160,288,3030658,1,0 +336,184,3031028,1,8 +256,216,3031214,1,0 +176,184,3031399,1,8 +304,96,3031769,21,8 +208,96,3031954,1,0 +256,32,3032139,1,4 +256,32,3032880,37,8 +48,280,3033436,1,0 +48,280,3033621,1,0 +192,212,3033991,1,0 +328,292,3034362,1,8 +464,216,3034732,1,0 +464,216,3034917,2,0,B|397:316|291:313|235:285|185:249|132:157|191:49,1,500,0|8 +320,48,3036214,1,0 +256,196,3036584,5,0 +320,336,3036954,1,0 +416,208,3037325,1,8 +256,195,3037695,2,0,B|122:154|122:154|189:218|155:337|25:311,2,400 +256,196,3039917,1,0 +256,36,3040288,1,8 +256,195,3040658,2,0,B|389:154|389:154|322:218|356:337|486:311,1,400 +476,152,3041769,1,8 +352,52,3042139,5,0 +316,108,3042325,1,0 +256,136,3042510,1,0 +196,108,3042695,1,8 +160,52,3042880,1,0 +256,232,3043251,1,8 +256,40,3043621,1,0 +256,40,3043806,1,0 +256,40,3043991,5,4 +256,40,3044732,1,8 +464,280,3045288,1,0 +464,280,3045473,1,0 +320,212,3045843,1,0 +184,292,3046214,1,8 +48,216,3046584,1,0 +48,216,3046769,2,0,B|114:316|220:313|276:285|326:249|379:157|320:49,1,500,0|8 +192,48,3048065,1,0 +256,196,3048436,5,0 +192,336,3048806,1,0 +96,208,3049176,1,8 +256,196,3049547,2,0,B|389:236|389:236|322:173|356:54|486:80,2,400 +256,196,3051769,1,0 +256,355,3052139,1,8 +256,196,3052510,2,0,B|122:236|122:236|189:173|155:54|25:80,1,400 +36,239,3053621,1,8 +160,339,3053991,5,0 +196,283,3054176,1,0 +256,255,3054362,1,0 +316,283,3054547,1,8 +352,339,3054732,1,0 +256,159,3055102,1,8 +256,351,3055473,1,0 +256,351,3055658,1,0 +256,351,3055843,5,4 +256,36,3056584,1,8 +256,260,3057139,1,0 +256,260,3057325,2,0,B|256:112,1,132.00000125885 +312,96,3057880,1,0 +316,160,3058065,1,0 +256,188,3058251,1,0 +196,160,3058436,1,0 +200,96,3058621,1,0 +256,64,3058806,5,4 +332,348,3059547,2,0,B|289:366|256:333|256:308|256:308|256:333|222:366|181:348,1,200,8|0 +80,192,3060288,1,8 +256,88,3060658,1,0 +432,192,3061028,1,8 +340,39,3061399,2,0,B|376:115|340:177|285:203|229:203|174:177|138:115|174:39,2,400 +432,192,3063251,5,8 +256,296,3063621,1,0 +80,192,3063991,1,8 +172,345,3064362,2,0,B|136:269|172:207|227:181|283:181|338:207|374:269|338:345,2,400 +356,48,3066584,6,0,B|156:48,1,200,0|8 +52,196,3067325,1,0 +174,338,3067695,2,0,B|321:183|321:183|256:126|256:126|190:183|190:183|337:338,1,600,8|0 +464,192,3069176,1,8 +464,192,3069362,1,8 +464,192,3069547,1,0 +341,61,3069917,6,0,B|309:102|257:126|200:102|170:59,1,200,8|0 +19,154,3070658,2,0,B|49:231|123:301|196:315|255:330|314:315|392:300|461:231|492:152,1,600,4|0 +480,336,3072139,5,8 +384,80,3072510,1,0 +256,288,3072880,1,8 +128,80,3073251,1,0 +32,336,3073621,1,8 +97,274,3073806,1,0 +171,219,3073991,2,0,B|202:175|257:161|311:175|342:219,1,200,0|8 +344,39,3074732,6,0,B|311:82|258:96|203:82|171:39,1,200,0|8 +160,224,3075473,1,0 +352,224,3075843,1,8 +256,352,3076214,1,0 +256,352,3076954,5,0 +256,192,3077325,1,8 +120,76,3077695,2,0,B|147:143|226:120|256:102|256:37|256:37|256:102|292:120|368:144|400:65,1,400 +376,248,3078806,5,8 +256,344,3079176,1,0 +256,140,3079547,1,0 +136,248,3079917,1,0 +256,44,3080288,1,8 +376,248,3080658,1,0 +340,172,3080843,1,8 +256,140,3081028,1,8 +172,172,3081214,1,8 +136,248,3081399,1,8 +256,344,3081769,21,8 +488,104,3082510,2,0,B|500:97|521:64|516:9|478:-2|456:-23|369:-26|316:12|357:88|357:122|390:190|423:190|491:190|495:255|495:288|495:288|496:323|465:380|423:389|358:386|328:339|323:322|323:289|325:257|345:218|345:218|56:190|256:-177|457:190|157:221|157:221|189:250|198:289|190:322|190:338|156:389|90:389|51:373|21:337|19:289|19:289|20:240|33:208|90:190|123:190|156:122|156:88|169:17|140:-9|52:-29|18:-4|-9:55|4:88|27:110,1,2200,4|0 +72,192,3103251,5,0 +168,332,3103621,1,0 +344,332,3103991,1,0 +440,192,3104362,1,0 +256,148,3104732,1,0 +256,148,3105102,1,0 +168,296,3105473,5,0 +200,348,3105658,1,0 +256,364,3105843,1,0 +312,348,3106028,1,0 +344,296,3106214,2,0,B|272:208|348:88|472:128,1,264.0000025177 +500,268,3107510,1,0 +500,268,3107695,1,0 +400,236,3108065,1,0 +300,204,3108436,1,0 +300,100,3108806,1,0 +168,88,3109176,6,0,B|240:176|164:296|40:256,1,264.0000025177 +12,116,3110473,1,0 +12,116,3110658,1,0 +112,148,3111028,1,0 +212,180,3111399,1,0 +212,284,3111769,1,0 +344,288,3112139,6,0,B|410:277|421:196|403:192|403:192|421:189|410:108|344:97,2,264.0000025177 +256,192,3113991,1,0 +168,96,3114362,2,0,B|101:106|90:187|108:191|108:191|90:194|101:275|168:286,1,264.0000025177 +344,284,3115473,1,0 +256,192,3115843,1,0 +168,100,3116214,1,0 +196,44,3116399,1,0 +256,20,3116584,1,0 +316,44,3116769,1,0 +344,100,3116954,1,0 +256,192,3117325,1,0 +168,300,3117695,1,0 +168,300,3117880,1,0 +168,300,3118065,6,0,B|208:344|312:364|448:344|444:164|432:108|372:56|288:-28|144:4|64:152|140:248,2,800,4|8|8 +256,192,3121121,12,8,3122510 +256,192,3122602,12,8,3123991 +256,192,3124084,12,8,3126214 +108,240,3126584,5,0 +168,72,3126954,1,0 +348,72,3127325,1,0 +404,240,3127695,1,8 +256,360,3128065,1,8 +192,312,3128251,1,8 +212,232,3128436,1,8 +300,232,3128621,1,0 +320,312,3128806,1,8 +344,56,3129176,6,0,B|340:104|340:104|288:136,1,100,8|0 +216,131,3129547,2,0,B|171:103|171:103|167:55,1,100,8|0 +256,44,3129917,5,4 +256,44,3130658,1,8 +464,280,3131214,1,0 +464,280,3131399,1,0 +320,212,3131769,1,0 +184,292,3132139,1,8 +48,216,3132510,1,0 +48,216,3132695,2,0,B|115:316|221:313|277:285|327:249|380:157|321:49,1,500,0|8 +192,48,3133991,1,0 +256,196,3134362,5,0 +192,336,3134732,1,0 +96,208,3135102,1,8 +256,195,3135473,2,0,B|390:154|390:154|323:218|357:337|487:311,2,400 +256,196,3137695,5,0 +256,36,3138065,1,8 +256,195,3138436,2,0,B|123:154|123:154|190:218|156:337|26:311,1,400 +36,152,3139547,1,8 +160,52,3139917,1,0 +196,108,3140102,1,0 +256,136,3140288,1,0 +316,108,3140473,1,8 +352,52,3140658,1,0 +256,232,3141028,5,8 +256,40,3141399,1,0 +256,40,3141584,1,0 +256,40,3141769,1,4 +378,372,3142510,6,0,B|348:306|260:264|172:297|136:371,1,300,8|0 +156,204,3143251,2,0,B|372:204,1,200,8|8 +296,32,3143991,2,0,B|314:70|296:101|268:115|240:115|212:101|194:70|212:32,1,200,8|0 +126,127,3144547,2,0,B|174:192|256:213|337:192|385:127,1,300 +428,304,3145473,5,8 +256,364,3145843,1,0 +84,304,3146214,1,0 +332,212,3146584,6,0,B|322:176|258:152|258:152|192:172|180:213,1,200,0|8 +104,72,3147325,2,0,B|189:109|256:42|256:-7|256:-7|256:42|322:109|406:72,1,400 +480,224,3148436,1,8 +360,322,3148806,2,0,B|325:339|290:339|256:322|221:304|204:252|221:201|256:183|290:201|308:252|290:304|256:322|221:339|187:339|152:322,1,400 +32,224,3149917,5,8 +173,146,3150288,2,0,B|181:115|213:90|254:84|254:84|281:74|303:41|281:9|257:3|229:9|201:41|227:74|254:84|254:84|297:90|324:111|338:152,1,400 +492,232,3151399,1,8 +352,344,3151769,2,0,B|312:312|312:312|304:264,1,100,8|0 +392,256,3152139,2,0,B|384:208|384:208|344:176,1,100,8|0 +328,88,3152510,1,8 +256,144,3152695,1,0 +184,88,3152880,1,8 +168,176,3153065,2,0,B|128:208|128:208|120:256,1,100,0|8 +208,264,3153436,2,0,B|200:312|200:312|160:344,1,100,0|4 +160,56,3154362,5,8 +396,92,3154917,1,0 +396,92,3155102,1,0 +336,240,3155473,1,0 +176,240,3155843,1,8 +116,91,3156214,2,0,B|51:116|28:160|20:208|72:284|148:287|204:212,1,300 +176,240,3156954,1,0 +288,116,3157325,1,8 +116,92,3157695,5,0 +116,92,3157880,1,0 +116,92,3158065,1,0 +176,240,3158436,1,0 +336,240,3158806,1,8 +396,91,3159176,2,0,B|460:116|484:160|492:208|440:284|363:287|308:212,1,300 +336,240,3159917,2,0,B|372:288|288:332|256:256|256:256|224:332|140:288|176:240,1,300 +175,240,3160658,1,0 +256,80,3161028,5,0 +388,320,3161399,1,0 +124,320,3161769,1,8 +96,104,3162139,2,0,B|132:10|259:-40|374:4|421:109,1,400 +256,192,3163251,1,8 +96,280,3163621,2,0,B|132:373|259:424|374:379|421:274,1,400 +366,112,3164732,6,0,B|320:92|320:92|300:48,1,100,8|0 +212,46,3165102,2,0,B|192:92|192:92|146:112,1,100,8|0 +207,184,3165473,2,0,B|146:207|146:207|67:176|67:176|99:348|99:348|177:317|177:317|224:348|224:348|256:333|256:333|287:348|287:348|334:317|334:317|412:348|412:348|444:176|444:176|365:207|365:207|303:184,1,1000,4|0 +256,24,3167695,5,8 +396,91,3168065,2,0,B|461:116|484:160|492:208|440:284|364:287|308:212,1,300 +336,240,3168806,1,0 +224,116,3169176,1,8 +396,92,3169547,5,0 +396,92,3169732,1,0 +396,92,3169917,1,0 +336,240,3170288,1,0 +176,240,3170658,1,8 +116,91,3171028,2,0,B|52:116|28:160|20:208|72:284|149:287|204:212,1,300 +176,240,3171769,2,0,B|140:288|224:332|256:256|256:256|288:332|372:288|336:240,1,300 +337,240,3172510,1,0 +256,80,3172880,5,0 +124,320,3173251,1,0 +388,320,3173621,1,8 +416,104,3173991,2,0,B|380:10|253:-40|138:4|91:109,1,400 +256,192,3175102,1,8 +104,304,3175473,2,0,B|189:267|256:334|256:383|256:383|256:334|322:267|406:304,1,400,8|0 +386,195,3176460,5,0 +309,126,3176583,2,0,B|341:62,2,66.6666666666667,8|0|8 +203,126,3176955,2,0,B|171:62,2,66.6666666666667,8|0|8 +126,195,3177325,1,4 diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index 18555574ba..35cceaf6fa 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs @@ -7,17 +7,17 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using OpenTK.Graphics; +using osu.Framework.Lists; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatSyncedContainer : OsuTestCase + public class TestCaseBeatSyncedContainer : OsuTestCase { private readonly MusicController mc; diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs new file mode 100644 index 0000000000..c09b987407 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -0,0 +1,394 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseBeatmapCarousel : OsuTestCase + { + private TestBeatmapCarousel carousel; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), + }; + + + private readonly Stack selectedSets = new Stack(); + + private BeatmapInfo currentSelection; + + private const int set_count = 5; + + [BackgroundDependencyLoader] + private void load() + { + Add(carousel = new TestBeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + }); + + List beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + + carousel.SelectionChanged = s => currentSelection = s; + + AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); + + AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); + + testTraversal(); + testFiltering(); + testRandom(); + testAddRemove(); + testSorting(); + + testRemoveAll(); + testEmptyTraversal(); + testHiding(); + } + + private void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + private void checkSelected(int set, int? diff = null) => + AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => + { + if (diff != null) + return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + + return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); + }); + + private void setSelected(int set, int diff) => + AddStep($"select set{set} diff{diff}", () => + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + + private void advanceSelection(bool diff, int direction = 1, int count = 1) + { + if (count == 1) + AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff)); + else + { + AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff), count); + } + } + + private void checkVisibleItemCount(bool diff, int count) => + AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => + carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); + + private void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + private void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); + + private void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + /// + /// Test keyboard traversal + /// + private void testTraversal() + { + advanceSelection(direction: 1, diff: false); + checkSelected(1, 1); + + advanceSelection(direction: 1, diff: true); + checkSelected(1, 2); + + advanceSelection(direction: -1, diff: false); + checkSelected(set_count, 1); + + advanceSelection(direction: -1, diff: true); + checkSelected(set_count - 1, 3); + + advanceSelection(diff: false); + advanceSelection(diff: false); + checkSelected(1, 2); + + advanceSelection(direction: -1, diff: true); + advanceSelection(direction: -1, diff: true); + checkSelected(set_count, 3); + } + + /// + /// Test filtering + /// + private void testFiltering() + { + // basic filtering + + setSelected(1, 1); + + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); + checkVisibleItemCount(diff: false, count: 1); + checkVisibleItemCount(diff: true, count: 3); + checkSelected(3, 1); + + advanceSelection(diff: true, count: 4); + checkSelected(3, 2); + + AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); + AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + checkVisibleItemCount(diff: false, count: set_count); + checkVisibleItemCount(diff: true, count: 3); + + // test filtering some difficulties (and keeping current beatmap set selected). + + setSelected(1, 2); + AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); + checkSelected(1, 1); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + checkSelected(1, 1); + + AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); + + checkVisibleItemCount(false, 0); + checkVisibleItemCount(true, 0); + AddAssert("Selection is null", () => currentSelection == null); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + + AddAssert("Selection is non-null", () => currentSelection != null); + } + + /// + /// Test random non-repeating algorithm + /// + private void testRandom() + { + setSelected(1, 1); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + prevRandom(); + ensureRandomFetchSuccess(); + prevRandom(); + ensureRandomFetchSuccess(); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + nextRandom(); + AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); + } + + /// + /// Test adding and removing beatmap sets + /// + private void testAddRemove() + { + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); + + checkVisibleItemCount(false, set_count + 2); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2))); + + checkVisibleItemCount(false, set_count + 1); + + setSelected(set_count + 1, 1); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1))); + + checkVisibleItemCount(false, set_count); + + checkSelected(set_count); + } + + /// + /// Test sorting + /// + private void testSorting() + { + AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); + AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); + } + + private void testRemoveAll() + { + setSelected(2, 1); + AddAssert("Selection is non-null", () => currentSelection != null); + + AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); + checkSelected(2); + + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + checkSelected(1); + + AddUntilStep(() => + { + if (!carousel.BeatmapSets.Any()) return true; + + carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); + return false; + }, "Remove all"); + + checkNoSelection(); + } + + private void testEmptyTraversal() + { + advanceSelection(direction: 1, diff: false); + checkNoSelection(); + + advanceSelection(direction: 1, diff: true); + checkNoSelection(); + + advanceSelection(direction: -1, diff: false); + checkNoSelection(); + + advanceSelection(direction: -1, diff: true); + checkNoSelection(); + } + + private void testHiding() + { + var hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); + setSelected(1, 1); + + checkVisibleItemCount(true, 2); + advanceSelection(true); + checkSelected(1, 3); + + setHidden(3); + checkSelected(1, 1); + + setHidden(2, false); + advanceSelection(true); + checkSelected(1, 2); + + setHidden(1); + checkSelected(1, 2); + + setHidden(2); + checkNoSelection(); + + void setHidden(int diff, bool hidden = true) + { + AddStep((hidden ? "" : "un") + $"hide diff {diff}", () => + { + hidingSet.Beatmaps[diff - 1].Hidden = hidden; + carousel.UpdateBeatmapSet(hidingSet); + }); + } + } + + private BeatmapSetInfo createTestBeatmapSet(int i) + { + return new BeatmapSetInfo + { + ID = i, + OnlineBeatmapSetID = i, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = i, + // Create random metadata, then we can check if sorting works based on these + Artist = $"peppy{i.ToString().PadLeft(6, '0')}", + Title = $"test set #{i}!", + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, i - 1)), 5)) + }, + Beatmaps = new List(new[] + { + new BeatmapInfo + { + OnlineBeatmapID = i * 10, + Path = "normal.osu", + Version = "Normal", + StarDifficulty = 2, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = i * 10 + 1, + Path = "hard.osu", + Version = "Hard", + StarDifficulty = 5, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 5, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = i * 10 + 2, + Path = "insane.osu", + Version = "Insane", + StarDifficulty = 6, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 7, + } + }, + }), + }; + } + + private class TestBeatmapCarousel : BeatmapCarousel + { + public new List Items => base.Items; + + public bool PendingFilterTask => FilterTask != null; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index 1a64994d0e..a3c1ad307f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual { [TestFixture] [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - internal class TestCaseBeatmapDetailArea : OsuTestCase + public class TestCaseBeatmapDetailArea : OsuTestCase { public TestCaseBeatmapDetailArea() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs index 248ec6d43d..62aced7423 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect beatmap details")] - internal class TestCaseBeatmapDetails : OsuTestCase + public class TestCaseBeatmapDetails : OsuTestCase { public TestCaseBeatmapDetails() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs new file mode 100644 index 0000000000..3a50e43239 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -0,0 +1,161 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseBeatmapInfoWedge : OsuTestCase + { + private RulesetStore rulesets; + private TestBeatmapInfoWedge infoWedge; + private readonly List beatmaps = new List(); + private readonly Bindable beatmap = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, RulesetStore rulesets) + { + this.rulesets = rulesets; + + beatmap.BindTo(game.Beatmap); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(infoWedge = new TestBeatmapInfoWedge + { + Size = new Vector2(0.5f, 245), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20 } + }); + + AddStep("show", () => + { + infoWedge.State = Visibility.Visible; + infoWedge.UpdateBeatmap(beatmap); + }); + + AddWaitStep(3); + + AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); + + AddWaitStep(3); + + AddStep("show", () => { infoWedge.State = Visibility.Visible; }); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + var ruleset = rulesetInfo.CreateInstance(); + beatmaps.Add(createTestBeatmap(rulesetInfo)); + + var name = rulesetInfo.ShortName; + selectBeatmap(name); + + // TODO: adjust cases once more info is shown for other gamemodes + switch (ruleset) + { + case OsuRuleset osu: + testOsuBeatmap(osu); + testInfoLabels(5); + break; + default: + testInfoLabels(2); + break; + } + } + + testNullBeatmap(); + } + + private void testOsuBeatmap(OsuRuleset ruleset) + { + AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version"); + AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist"); + AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author")); + } + + private void testInfoLabels(int expectedCount) + { + AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); + AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); + } + + private void testNullBeatmap() + { + selectNullBeatmap(); + AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); + AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); + } + + private void selectBeatmap(string name) + { + var infoBefore = infoWedge.Info; + + AddStep($"select {name} beatmap", () => + { + beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name)); + infoWedge.UpdateBeatmap(beatmap); + }); + + AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); + } + + private void selectNullBeatmap() + { + AddStep("select null beatmap", () => + { + beatmap.Value = beatmap.Default; + infoWedge.UpdateBeatmap(beatmap); + }); + } + + private Beatmap createTestBeatmap(RulesetInfo ruleset) + { + List objects = new List(); + for (double i = 0; i < 50000; i += 1000) + objects.Add(new HitObject { StartTime = i }); + + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + AuthorString = $"{ruleset.ShortName}Author", + Artist = $"{ruleset.ShortName}Artist", + Source = $"{ruleset.ShortName}Source", + Title = $"{ruleset.ShortName}Title" + }, + Ruleset = ruleset, + StarDifficulty = 6, + Version = $"{ruleset.ShortName}Version" + }, + HitObjects = objects + }; + } + + private class TestBeatmapInfoWedge : BeatmapInfoWedge + { + public new BufferedWedgeInfo Info => base.Info; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs index e114fac96e..12f1cf29ee 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs @@ -10,7 +10,7 @@ using OpenTK.Input; namespace osu.Game.Tests.Visual { [Description("bottom beatmap details")] - internal class TestCaseBeatmapOptionsOverlay : OsuTestCase + public class TestCaseBeatmapOptionsOverlay : OsuTestCase { public TestCaseBeatmapOptionsOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index c24e13b7fb..6a2161809f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatmapSetOverlay : OsuTestCase + public class TestCaseBeatmapSetOverlay : OsuTestCase { private readonly BeatmapSetOverlay overlay; diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs index 50abd11e79..7760bffdaf 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs @@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual { - internal class TestCaseBreadcrumbs : OsuTestCase + public class TestCaseBreadcrumbs : OsuTestCase { public TestCaseBreadcrumbs() { diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs index dcb3b74654..328bbaedba 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace osu.Game.Tests.Visual { - internal class TestCaseBreakOverlay : OsuTestCase + public class TestCaseBreakOverlay : OsuTestCase { private readonly BreakOverlay breakOverlay; diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs index d260de69f1..9d796617de 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs @@ -9,7 +9,7 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { - internal class TestCaseButtonSystem : OsuTestCase + public class TestCaseButtonSystem : OsuTestCase { public TestCaseButtonSystem() { diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 85ee224a5e..5bfa6c1de4 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { [Description("Testing chat api and overlay")] - internal class TestCaseChatDisplay : OsuTestCase + public class TestCaseChatDisplay : OsuTestCase { public TestCaseChatDisplay() { diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs index 6f5cb398d7..45e6464149 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { - internal class TestCaseContextMenu : OsuTestCase + public class TestCaseContextMenu : OsuTestCase { private const int start_time = 0; private const int duration = 1000; diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs index f1aba908f0..6dd84b204f 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual { - internal class TestCaseDialogOverlay : OsuTestCase + public class TestCaseDialogOverlay : OsuTestCase { public TestCaseDialogOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs index a51cf8ca95..3bbe6de921 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseDrawableRoom : OsuTestCase + public class TestCaseDrawableRoom : OsuTestCase { private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs index e5692b29de..a6ef3b309a 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawings.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Tournament.Teams; namespace osu.Game.Tests.Visual { [Description("for tournament use")] - internal class TestCaseDrawings : OsuTestCase + public class TestCaseDrawings : OsuTestCase { public TestCaseDrawings() { diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs new file mode 100644 index 0000000000..d52f27f4ab --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit.Screens.Compose; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorCompose : OsuTestCase + { + private readonly Random random; + private readonly Compose compose; + + public TestCaseEditorCompose() + { + random = new Random(1337); + + Add(compose = new Compose()); + AddStep("Next beatmap", nextBeatmap); + } + + private OsuGameBase osuGame; + private BeatmapManager beatmaps; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame, BeatmapManager beatmaps) + { + this.osuGame = osuGame; + this.beatmaps = beatmaps; + + compose.Beatmap.BindTo(osuGame.Beatmap); + } + + private void nextBeatmap() + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count == 0) + return; + + var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; + osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs new file mode 100644 index 0000000000..f8669cde4b --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorComposeRadioButtons : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; + + public TestCaseEditorComposeRadioButtons() + { + RadioButtonCollection collection; + Add(collection = new RadioButtonCollection + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 150, + Items = new[] + { + new RadioButton("Item 1", () => { }), + new RadioButton("Item 2", () => { }), + new RadioButton("Item 3", () => { }), + new RadioButton("Item 4", () => { }), + new RadioButton("Item 5", () => { }) + } + }); + + for (int i = 0; i < collection.Items.Count; i++) + { + int l = i; + AddStep($"Select item {l + 1}", () => collection.Items[l].Select()); + AddStep($"Deselect item {l + 1}", () => collection.Items[l].Deselect()); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs new file mode 100644 index 0000000000..6b7dedf9cf --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorSelectionLayer : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) }; + + public TestCaseEditorSelectionLayer() + { + var playfield = new OsuEditPlayfield + { + new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }), + new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }), + new DrawableSlider(new Slider + { + ControlPoints = new List + { + new Vector2(128, 256), + new Vector2(344, 256), + }, + Distance = 400, + Position = new Vector2(128, 256), + Velocity = 1, + TickDistance = 100, + Scale = 0.5f + }) + }; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(new StopwatchClock()), + Child = playfield + }, + new SelectionLayer(playfield) + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index c35355aefd..ef7404ad49 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -14,7 +14,7 @@ using osu.Framework.Configuration; namespace osu.Game.Tests.Visual { - internal class TestCaseEditorSummaryTimeline : OsuTestCase + public class TestCaseEditorSummaryTimeline : OsuTestCase { private const int length = 60000; private readonly Random random; @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) }); for (int i = 0; i < random.Next(1, 5); i++) - b.ControlPointInfo.SoundPoints.Add(new SoundControlPoint { Time = random.Next(0, length) }); + b.ControlPointInfo.SamplePoints.Add(new SampleControlPoint { Time = random.Next(0, length) }); b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)]; for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++) diff --git a/osu.Game.Tests/Visual/TestCaseGamefield.cs b/osu.Game.Tests/Visual/TestCaseGamefield.cs index 0d8f4cb5f7..9dce0cfe01 100644 --- a/osu.Game.Tests/Visual/TestCaseGamefield.cs +++ b/osu.Game.Tests/Visual/TestCaseGamefield.cs @@ -5,7 +5,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Tests.Visual { - internal class TestCaseGamefield : OsuTestCase + public class TestCaseGamefield : OsuTestCase { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs new file mode 100644 index 0000000000..bd5772d3bb --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -0,0 +1,258 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Logging; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [Description("player pause/fail screens")] + public class TestCaseGameplayMenuOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; + + private FailOverlay failOverlay; + private PauseContainer.PauseOverlay pauseOverlay; + + [BackgroundDependencyLoader] + private void load() + { + Add(pauseOverlay = new PauseContainer.PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + + Add(failOverlay = new FailOverlay + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + + var retryCount = 0; + + AddStep("Add retry", () => + { + retryCount++; + pauseOverlay.Retries = failOverlay.Retries = retryCount; + }); + + AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility()); + AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility()); + + testHideResets(); + + testEnterWithoutSelection(); + testKeyUpFromInitial(); + testKeyDownFromInitial(); + testKeyUpWrapping(); + testKeyDownWrapping(); + + testMouseSelectionAfterKeySelection(); + testKeySelectionAfterMouseSelection(); + + testMouseDeselectionResets(); + + testClickSelection(); + testEnterKeySelection(); + } + + /// + /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. + /// + private void testHideResets() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnHover(null)); + AddStep("Hide overlay", () => failOverlay.Hide()); + + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); + } + + /// + /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. + /// + private void testEnterWithoutSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter })); + AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow from the initial state selects the last button. + /// + private void testKeyUpFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow from the initial state selects the first button. + /// + private void testKeyDownFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyUpWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyDownWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. + /// + private void testMouseSelectionAfterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); + AddAssert("Second button selected", () => secondButton.Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button. + /// + private void testKeySelectionAfterMouseSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Second button not selected", () => !secondButton.Selected); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state. + /// + private void testMouseDeselectionResets() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null)); + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that clicking on a button correctly causes a click event for that button. + /// + private void testClickSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Click retry button", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnClick(); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + } + + /// + /// Tests that pressing the enter key with a button selected correctly causes a click event for that button. + /// + private void testEnterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Select second button", () => + { + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + }); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Press enter", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/TestCaseGraph.cs index fb1a3ef3f6..4d3fd267db 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseGraph.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseGraph : OsuTestCase + public class TestCaseGraph : OsuTestCase { public TestCaseGraph() { diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs new file mode 100644 index 0000000000..c60b21fa20 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Historical; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseHistoricalSection : OsuTestCase + { + public override IReadOnlyList RequiredTypes => + new[] + { + typeof(HistoricalSection), + typeof(PaginatedMostPlayedBeatmapContainer), + typeof(DrawableMostPlayedRow), + typeof(DrawableProfileRow) + }; + + public TestCaseHistoricalSection() + { + HistoricalSection section; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }); + + Add(new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = section = new HistoricalSection(), + }); + + AddStep("Show peppy", () => section.User.Value = new User { Id = 2 }); + AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index df122b7132..7fddc92ee6 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -8,7 +8,7 @@ using OpenTK.Input; namespace osu.Game.Tests.Visual { - internal class TestCaseKeyCounter : OsuTestCase + public class TestCaseKeyCounter : OsuTestCase { public TestCaseKeyCounter() { diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 9d6fb3a4ec..a01dbb7a6b 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -6,14 +6,45 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; +using osu.Framework.Allocation; using OpenTK; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect leaderboard")] - internal class TestCaseLeaderboard : OsuTestCase + public class TestCaseLeaderboard : OsuTestCase { - private readonly Leaderboard leaderboard; + private RulesetStore rulesets; + + private readonly FailableLeaderboard leaderboard; + + public TestCaseLeaderboard() + { + Add(leaderboard = new FailableLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = LeaderboardScope.Global, + }); + + AddStep(@"New Scores", newScores); + AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); + AddStep(@"Real beatmap", realBeatmap); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } private void newScores() { @@ -204,17 +235,44 @@ namespace osu.Game.Tests.Visual leaderboard.Scores = scores; } - public TestCaseLeaderboard() + private void realBeatmap() { - Add(leaderboard = new Leaderboard + leaderboard.Beatmap = new BeatmapInfo { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); + StarDifficulty = 1.36, + Version = @"BASIC", + OnlineBeatmapID = 1113057, + Ruleset = rulesets.GetRuleset(0), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 6.5f, + OverallDifficulty = 6.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 115000, + CircleCount = 265, + SliderCount = 71, + PlayCount = 47906, + PassCount = 19899, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }; + } - AddStep(@"New Scores", newScores); - newScores(); + private class FailableLeaderboard : Leaderboard + { + public void SetRetrievalState(PlaceholderState state) + { + PlaceholderState = state; + } } } } diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs index fbee27668c..bd69719f10 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs @@ -9,7 +9,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseMedalOverlay : OsuTestCase + public class TestCaseMedalOverlay : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/TestCaseMenuOverlays.cs b/osu.Game.Tests/Visual/TestCaseMenuOverlays.cs deleted file mode 100644 index 94a69f0029..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMenuOverlays.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("player pause/fail screens")] - internal class TestCaseMenuOverlays : OsuTestCase - { - public TestCaseMenuOverlays() - { - FailOverlay failOverlay; - PauseContainer.PauseOverlay pauseOverlay; - - var retryCount = 0; - - Add(pauseOverlay = new PauseContainer.PauseOverlay - { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); - Add(failOverlay = new FailOverlay - { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); - - AddStep(@"Pause", delegate - { - if (failOverlay.State == Visibility.Visible) - { - failOverlay.Hide(); - } - pauseOverlay.Show(); - }); - AddStep("Fail", delegate - { - if (pauseOverlay.State == Visibility.Visible) - { - pauseOverlay.Hide(); - } - failOverlay.Show(); - }); - AddStep("Add Retry", delegate - { - retryCount++; - pauseOverlay.Retries = retryCount; - failOverlay.Retries = retryCount; - }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 5270ac0dc9..e535da3fcc 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -8,17 +8,25 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using OpenTK; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; namespace osu.Game.Tests.Visual { [Description("mod select and icon display")] - internal class TestCaseMods : OsuTestCase + public class TestCaseMods : OsuTestCase { - private ModSelectOverlay modSelect; - private ModDisplay modDisplay; + private const string unranked_suffix = " (Unranked)"; private RulesetStore rulesets; - + private ModDisplay modDisplay; + private TestModSelectOverlay modSelect; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -30,7 +38,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(modSelect = new ModSelectOverlay + Add(modSelect = new TestModSelectOverlay { RelativeSizeAxes = Axes.X, Origin = Anchor.BottomCentre, @@ -48,9 +56,156 @@ namespace osu.Game.Tests.Visual modDisplay.Current.BindTo(modSelect.SelectedMods); AddStep("Toggle", modSelect.ToggleVisibility); + AddStep("Hide", modSelect.Hide); + AddStep("Show", modSelect.Show); - foreach (var ruleset in rulesets.AvailableRulesets) - AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + Ruleset ruleset = rulesetInfo.CreateInstance(); + AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); + + switch (ruleset) { + case OsuRuleset or: + testOsuMods(or); + break; + } + } + } + + private void testOsuMods(OsuRuleset ruleset) + { + var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction); + var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease); + var assistMods = ruleset.GetModsFor(ModType.Special); + + var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); + var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + + testSingleMod(noFailMod); + testMultiMod(doubleTimeMod); + testIncompatibleMods(noFailMod, autoPilotMod); + testDeselectAll(easierMods.Where(m => !(m is MultiMod))); + testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextUnranked(autoPilotMod); + } + + private void testSingleMod(Mod mod) + { + selectNext(mod); + checkSelected(mod); + + selectPrevious(mod); + checkNotSelected(mod); + + selectNext(mod); + selectNext(mod); + checkNotSelected(mod); + + selectPrevious(mod); + selectPrevious(mod); + checkNotSelected(mod); + } + + private void testMultiMod(MultiMod multiMod) + { + foreach (var mod in multiMod.Mods) + { + selectNext(mod); + checkSelected(mod); + } + + for (int index = multiMod.Mods.Length - 1; index >= 0; index--) + selectPrevious(multiMod.Mods[index]); + + foreach (var mod in multiMod.Mods) + checkNotSelected(mod); + } + + private void testIncompatibleMods(Mod modA, Mod modB) + { + selectNext(modA); + checkSelected(modA); + checkNotSelected(modB); + + selectNext(modB); + checkSelected(modB); + checkNotSelected(modA); + + selectPrevious(modB); + checkNotSelected(modA); + checkNotSelected(modB); + } + + private void testDeselectAll(IEnumerable mods) + { + foreach (var mod in mods) + selectNext(mod); + + AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); + AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); + } + + private void testMultiplierTextColour(Mod mod, Color4 colour) + { + checkLabelColor(Color4.White); + selectNext(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(colour); + selectPrevious(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(Color4.White); + } + + private void testMultiplierTextUnranked(Mod mod) + { + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectNext(mod); + AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectPrevious(mod); + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + } + + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext()); + + private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious()); + + private void checkSelected(Mod mod) + { + AddAssert($"check {mod.Name} is selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + }); + } + + private void checkNotSelected(Mod mod) + { + AddAssert($"check {mod.Name} is not selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); + }); + } + + private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + + private class TestModSelectOverlay : ModSelectOverlay + { + public ModButton GetModButton(Mod mod) + { + var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); + return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; + public new TriangleButton DeselectAllButton => base.DeselectAllButton; + + public new Color4 LowMultiplierColour => base.LowMultiplierColour; + public new Color4 HighMultiplierColour => base.HighMultiplierColour; } } } diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs index 3c544bb968..16f2fab321 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseMusicController : OsuTestCase + public class TestCaseMusicController : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 3dca860909..a83cead213 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -1,21 +1,30 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; -using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; namespace osu.Game.Tests.Visual { - [TestFixture] - internal class TestCaseNotificationOverlay : OsuTestCase + public class TestCaseNotificationOverlay : OsuTestCase { private readonly NotificationOverlay manager; + private readonly List progressingNotifications = new List(); + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Notification), + typeof(ProgressNotification), + typeof(ProgressCompletionNotification), + typeof(SimpleNotification), + typeof(IHasCompletionTarget), + }; public TestCaseNotificationOverlay() { @@ -24,15 +33,20 @@ namespace osu.Game.Tests.Visual Content.Add(manager = new NotificationOverlay { Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Origin = Anchor.TopRight }); - AddToggleStep(@"show", state => manager.State = state ? Visibility.Visible : Visibility.Hidden); + SpriteText displayedCount = new SpriteText(); - AddStep(@"simple #1", sendNotification1); - AddStep(@"simple #2", sendNotification2); - AddStep(@"progress #1", sendProgress1); - AddStep(@"progress #2", sendProgress2); + Content.Add(displayedCount); + + manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; + + AddStep(@"toggle", manager.ToggleVisibility); + AddStep(@"simple #1", sendHelloNotification); + AddStep(@"simple #2", sendAmazingNotification); + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); AddStep(@"barrage", () => sendBarrage()); } @@ -41,16 +55,16 @@ namespace osu.Game.Tests.Visual switch (RNG.Next(0, 4)) { case 0: - sendNotification1(); + sendHelloNotification(); break; case 1: - sendNotification2(); + sendAmazingNotification(); break; case 2: - sendProgress1(); + sendUploadProgress(); break; case 3: - sendProgress2(); + sendDownloadProgress(); break; } @@ -80,28 +94,34 @@ namespace osu.Game.Tests.Visual } } - private void sendProgress2() + private void sendDownloadProgress() { - var n = new ProgressNotification { Text = @"Downloading Haitai..." }; + var n = new ProgressNotification + { + Text = @"Downloading Haitai...", + CompletionText = "Downloaded Haitai!", + }; manager.Post(n); progressingNotifications.Add(n); } - private readonly List progressingNotifications = new List(); - - private void sendProgress1() + private void sendUploadProgress() { - var n = new ProgressNotification { Text = @"Uploading to BSS..." }; + var n = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; manager.Post(n); progressingNotifications.Add(n); } - private void sendNotification2() + private void sendAmazingNotification() { manager.Post(new SimpleNotification { Text = @"You are amazing" }); } - private void sendNotification1() + private void sendHelloNotification() { manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); } diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs index c3a755f3ca..179f17ab50 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseOnScreenDisplay : OsuTestCase + public class TestCaseOnScreenDisplay : OsuTestCase { private FrameworkConfigManager config; private Bindable frameSyncMode; diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 7c070fd3df..18e40db064 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -13,43 +13,96 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Platform; namespace osu.Game.Tests.Visual { - internal class TestCasePlaySongSelect : OsuTestCase + public class TestCasePlaySongSelect : OsuTestCase { private BeatmapManager manager; private RulesetStore rulesets; private DependencyContainer dependencies; + private WorkingBeatmap defaultBeatmap; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SongSelect), + typeof(BeatmapCarousel), + + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), + }; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - [BackgroundDependencyLoader] - private void load() + private class TestSongSelect : PlaySongSelect { - PlaySongSelect songSelect; + public WorkingBeatmap CurrentBeatmap => Beatmap.Value; + public new BeatmapCarousel Carousel => base.Carousel; + } - if (manager == null) + [BackgroundDependencyLoader] + private void load(BeatmapManager baseManager) + { + TestSongSelect songSelect = null; + + var storage = new TestStorage(@"TestCasePlaySongSelect"); + + // this is by no means clean. should be replacing inside of OsuGameBase somehow. + var context = new OsuDbContext(); + + Func contextFactory = () => context; + + dependencies.Cache(rulesets = new RulesetStore(contextFactory)); + dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) { - var storage = new TestStorage(@"TestCasePlaySongSelect"); + DefaultBeatmap = defaultBeatmap = baseManager.GetWorkingBeatmap(null) + }); - // this is by no means clean. should be replacing inside of OsuGameBase somehow. - var context = new OsuDbContext(); + void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => + { + if (deleteMaps) manager.DeleteAll(); - Func contextFactory = () => context; + if (songSelect != null) + { + Remove(songSelect); + songSelect.Dispose(); + } - dependencies.Cache(rulesets = new RulesetStore(contextFactory)); - dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)); + Add(songSelect = new TestSongSelect()); + }); + loadNewSongSelect(true); + + AddWaitStep(3); + + AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + + AddStep("import test maps", () => + { for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); - } + }); - Add(songSelect = new PlaySongSelect()); + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + + loadNewSongSelect(); + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/TestCasePopupDialog.cs new file mode 100644 index 0000000000..ed9c47a253 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePopupDialog.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePopupDialog : OsuTestCase + { + public TestCasePopupDialog() + { + var popup = new PopupDialog + { + RelativeSizeAxes = Axes.Both, + State = Framework.Graphics.Containers.Visibility.Visible, + Icon = FontAwesome.fa_assistive_listening_systems, + HeaderText = @"This is a test popup", + BodyText = "I can say lots of stuff and even wrap my words!", + Buttons = new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = @"Yes. That you can.", + }, + new PopupDialogOkButton + { + Text = @"You're a fake!", + }, + } + }; + + Add(popup); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs new file mode 100644 index 0000000000..489fba6501 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using System.Collections.Generic; +using System; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseRankGraph : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankGraph), + typeof(LineGraph) + }; + + public TestCaseRankGraph() + { + RankGraph graph; + + var data = new int[89]; + var dataWithZeros = new int[89]; + var smallData = new int[89]; + + for (int i = 0; i < 89; i++) + data[i] = dataWithZeros[i] = (i + 1) * 1000; + + for (int i = 20; i < 60; i++) + dataWithZeros[i] = 0; + + for (int i = 79; i < 89; i++) + smallData[i] = 100000 - i * 1000; + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 150), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + graph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + } + } + }); + + AddStep("null user", () => graph.User.Value = null); + AddStep("rank only", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 123456, + PP = 12345, + } + }; + }); + + AddStep("with rank history", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = data, + } + }; + }); + + AddStep("with zero values", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = dataWithZeros, + } + }; + }); + + AddStep("small amount of data", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 12000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = smallData, + } + }; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 62c8a64916..8e92696e8d 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseReplay : TestCasePlayer + public class TestCaseReplay : TestCasePlayer { protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) { diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs index badb98e6b7..d795b51d34 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Play.ReplaySettings; namespace osu.Game.Tests.Visual { - internal class TestCaseReplaySettingsOverlay : OsuTestCase + public class TestCaseReplaySettingsOverlay : OsuTestCase { public TestCaseReplaySettingsOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index f1bbb8fed6..d0c5aa4939 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseResults : OsuTestCase + public class TestCaseResults : OsuTestCase { private BeatmapManager beatmaps; diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs index 51b6ae8e50..4f167329a0 100644 --- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs +++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseRoomInspector : OsuTestCase + public class TestCaseRoomInspector : OsuTestCase { private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs index 5a04000900..8f82c5e7a9 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs @@ -10,7 +10,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseScoreCounter : OsuTestCase + public class TestCaseScoreCounter : OsuTestCase { public TestCaseScoreCounter() { diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 63d798cd53..15a60386ef 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -1,23 +1,39 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseSettings : OsuTestCase + public class TestCaseSettings : OsuTestCase { private readonly SettingsOverlay settings; + private readonly DialogOverlay dialogOverlay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); public TestCaseSettings() { - Children = new[] { settings = new MainSettings() }; + settings = new MainSettings + { + State = Visibility.Visible + }; + Add(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - settings.ToggleVisibility(); + dependencies.Cache(dialogOverlay); + + Add(settings); } } } diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index 40c8baaac8..296c10a980 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -5,7 +5,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseSkipButton : OsuTestCase + public class TestCaseSkipButton : OsuTestCase { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs index 1e6886cda9..fe534bc679 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseSongProgress : OsuTestCase + public class TestCaseSongProgress : OsuTestCase { private readonly SongProgress progress; private readonly SongProgressGraph graph; diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index 1dad106cbe..aa9618b208 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -14,7 +14,7 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { - internal class TestCaseStoryboard : OsuTestCase + public class TestCaseStoryboard : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Beatmap.Storyboard.CreateDrawable(beatmapBacking); + storyboard = working.Storyboard.CreateDrawable(beatmapBacking); storyboard.Passing = false; storyboardContainer.Add(storyboard); diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs index 37905a1883..e2b4914558 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs @@ -10,7 +10,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseTextAwesome : OsuTestCase + public class TestCaseTextAwesome : OsuTestCase { public TestCaseTextAwesome() { diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs new file mode 100644 index 0000000000..9f538af09b --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Toolbar; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseToolbar : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ToolbarButton), + typeof(ToolbarModeSelector), + typeof(ToolbarModeButton), + typeof(ToolbarNotificationButton), + }; + + public TestCaseToolbar() + { + var toolbar = new Toolbar { State = Visibility.Visible }; + + Add(toolbar); + + var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + + void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); + + setNotifications(1); + setNotifications(2); + setNotifications(3); + setNotifications(0); + setNotifications(144); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs index bd5c10d147..866d1f56ef 100644 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual { [Description("mostly back button")] - internal class TestCaseTwoLayerButton : OsuTestCase + public class TestCaseTwoLayerButton : OsuTestCase { public TestCaseTwoLayerButton() { diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs index 8d94a0c90f..31f9789093 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseUserPanel : OsuTestCase + public class TestCaseUserPanel : OsuTestCase { public TestCaseUserPanel() { diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 90daf1e996..13b6509740 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -2,18 +2,35 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Overlays.Profile; using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseUserProfile : OsuTestCase + public class TestCaseUserProfile : OsuTestCase { + private readonly TestUserProfileOverlay profile; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(UserProfileOverlay), + typeof(RankGraph), + typeof(LineGraph), + }; + public TestCaseUserProfile() { - var profile = new UserProfileOverlay(); - Add(profile); + Add(profile = new TestUserProfileOverlay()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); AddStep("Show offline dummy", () => profile.ShowUser(new User { @@ -37,6 +54,9 @@ namespace osu.Game.Tests.Visual Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() } }, false)); + + checkSupporterTag(false); + AddStep("Show ppy", () => profile.ShowUser(new User { Username = @"peppy", @@ -44,6 +64,9 @@ namespace osu.Game.Tests.Visual Country = new Country { FullName = @"Australia", FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); + + checkSupporterTag(true); + AddStep("Show flyte", () => profile.ShowUser(new User { Username = @"flyte", @@ -51,8 +74,23 @@ namespace osu.Game.Tests.Visual Country = new Country { FullName = @"Japan", FlagName = @"JP" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); + AddStep("Hide", profile.Hide); AddStep("Show without reload", profile.Show); } + + private void checkSupporterTag(bool isSupporter) + { + AddUntilStep(() => profile.Header.User != null, "wait for load"); + if (isSupporter) + AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); + else + AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); + } + + private class TestUserProfileOverlay : UserProfileOverlay + { + public new ProfileHeader Header => base.Header; + } } } diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/TestCaseUserRanks.cs index eb0678203c..e7b8047882 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/TestCaseUserRanks.cs @@ -13,9 +13,9 @@ using System.Collections.Generic; namespace osu.Game.Tests.Visual { - internal class TestCaseUserRanks : OsuTestCase + public class TestCaseUserRanks : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableScore), typeof(RanksSection) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; public TestCaseUserRanks() { diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index fc21b86c5d..4c6efd439f 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -15,7 +15,7 @@ using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Tests.Visual { - internal class TestCaseWaveform : OsuTestCase + public class TestCaseWaveform : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 45a71d600f..4507d3d7f3 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -26,5 +26,8 @@ + + + \ No newline at end of file diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 171a1bdf75..64a9aa50a0 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -1,8 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using osu.Framework.Audio.Sample; + namespace osu.Game.Audio { + [Serializable] public class SampleInfo { public const string HIT_WHISTLE = @"hitwhistle"; @@ -10,6 +14,13 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; + public SampleChannel GetChannel(SampleManager manager) + { + var channel = manager.Get($"Gameplay/{Bank}-{Name}"); + channel.Volume.Value = Volume / 100.0; + return channel; + } + /// /// The bank to load the sample from. /// diff --git a/osu.Game/Audio/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs deleted file mode 100644 index 06dd716a4a..0000000000 --- a/osu.Game/Audio/SampleInfoList.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Audio -{ - public class SampleInfoList : List - { - public SampleInfoList() - { - } - - public SampleInfoList(IEnumerable elements) : base(elements) - { - } - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 35b6cc2b02..b639de640a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,20 +8,21 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO.Serialization; -using osu.Game.Storyboards; +using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps { /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap + public class Beatmap : IJsonSerializable where T : HitObject { public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); - public readonly List ComboColors = new List + public List ComboColors = new List { new Color4(17, 136, 170, 255), new Color4(102, 136, 0, 255), @@ -29,23 +30,21 @@ namespace osu.Game.Beatmaps new Color4(121, 9, 13, 255) }; + [JsonIgnore] public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; /// /// The HitObjects this Beatmap contains. /// + [JsonConverter(typeof(TypedListConverter))] public List HitObjects = new List(); /// /// Total amount of break time in the beatmap. /// + [JsonIgnore] public double TotalBreakTime => Breaks.Sum(b => b.Duration); - /// - /// The Beatmap's Storyboard. - /// - public Storyboard Storyboard = new Storyboard(); - /// /// Constructs a new beatmap. /// @@ -57,7 +56,6 @@ namespace osu.Game.Beatmaps Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; HitObjects = original?.HitObjects ?? HitObjects; - Storyboard = original?.Storyboard ?? Storyboard; if (original == null && Metadata == null) { diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 0b0fca8292..03fbf9a0a7 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; namespace osu.Game.Beatmaps { @@ -13,6 +14,7 @@ namespace osu.Game.Beatmaps public const float DEFAULT_DIFFICULTY = 5; [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 022d64db03..1da3dc8a54 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -12,9 +12,11 @@ using osu.Game.Rulesets; namespace osu.Game.Beatmaps { + [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } //TODO: should be in database @@ -38,13 +40,16 @@ namespace osu.Game.Beatmaps set { onlineBeatmapSetID = value > 0 ? value : null; } } + [JsonIgnore] public int BeatmapSetInfoID { get; set; } [Required] + [JsonIgnore] public BeatmapSetInfo BeatmapSet { get; set; } public BeatmapMetadata Metadata { get; set; } + [JsonIgnore] public int BaseDifficultyID { get; set; } public BeatmapDifficulty BaseDifficulty { get; set; } @@ -60,6 +65,7 @@ namespace osu.Game.Beatmaps [JsonProperty("file_sha2")] public string Hash { get; set; } + [JsonIgnore] public bool Hidden { get; set; } /// @@ -115,8 +121,11 @@ namespace osu.Game.Beatmaps // Metadata public string Version { get; set; } + [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } + public override string ToString() => $"{Metadata} [{Version}]"; + public bool Equals(BeatmapInfo other) { if (ID == 0 || other?.ID == 0) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5bdff54b20..f586e0862d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -25,6 +25,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Utils; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps { @@ -133,6 +134,7 @@ namespace osu.Game.Beatmaps var notification = new ProgressNotification { Text = "Beatmap import is initialising...", + CompletionText = "Import successful!", Progress = 0, State = ProgressNotificationState.Active, }; @@ -244,8 +246,9 @@ namespace osu.Game.Beatmaps return; } - ProgressNotification downloadNotification = new ProgressNotification + var downloadNotification = new ProgressNotification { + CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", }; @@ -339,6 +342,61 @@ namespace osu.Game.Beatmaps } } + public void UndeleteAll() + { + var deleteMaps = QueryBeatmapSets(bs => bs.DeletePending).ToList(); + + if (!deleteMaps.Any()) return; + + var notification = new ProgressNotification + { + CompletionText = "Restored all deleted beatmaps!", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var bs in deleteMaps) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Restoring ({i} of {deleteMaps.Count})"; + notification.Progress = (float)++i / deleteMaps.Count; + Undelete(bs); + } + + notification.State = ProgressNotificationState.Completed; + } + + public void Undelete(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Protected) + return; + + lock (importContext) + { + var context = importContext.Value; + + using (var transaction = context.BeginTransaction()) + { + context.ChangeTracker.AutoDetectChangesEnabled = false; + + var iFiles = new FileStore(() => context, storage); + var iBeatmaps = createBeatmapStore(() => context); + + undelete(iBeatmaps, iFiles, beatmapSet); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.SaveChanges(transaction); + } + } + } + /// /// Delete a beatmap difficulty. /// @@ -374,12 +432,9 @@ namespace osu.Game.Beatmaps /// A instance correlating to the provided . public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { - if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet == null) - throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); - if (beatmapInfo.Metadata == null) beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; @@ -495,17 +550,23 @@ namespace osu.Game.Beatmaps BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) - metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; + // check if a set already exists with the same online id. - beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) ?? new BeatmapSetInfo - { - OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, - Beatmaps = new List(), - Hash = hash, - Files = fileInfos, - Metadata = metadata - }; + if (metadata.OnlineBeatmapSetID != null) + beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID); + + if (beatmapSet == null) + beatmapSet = new BeatmapSetInfo + { + OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, + Beatmaps = new List(), + Hash = hash, + Files = fileInfos, + Metadata = metadata + }; + var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); @@ -518,19 +579,20 @@ namespace osu.Game.Beatmaps raw.CopyTo(ms); ms.Position = 0; - var decoder = BeatmapDecoder.GetDecoder(sr); - Beatmap beatmap = decoder.Decode(sr); + var decoder = Decoder.GetDecoder(sr); + Beatmap beatmap = decoder.DecodeBeatmap(sr); beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); - var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID); + var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID); if (existing == null) { - // TODO: Diff beatmap metadata with set metadata and leave it here if necessary - beatmap.BeatmapInfo.Metadata = null; + // Exclude beatmap-metadata if it's equal to beatmapset-metadata + if (metadata.Equals(beatmap.Metadata)) + beatmap.BeatmapInfo.Metadata = null; RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); @@ -569,23 +631,11 @@ namespace osu.Game.Beatmaps { try { - Beatmap beatmap; - - BeatmapDecoder decoder; using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { - decoder = BeatmapDecoder.GetDecoder(stream); - beatmap = decoder.Decode(stream); + Decoder decoder = Decoder.GetDecoder(stream); + return decoder.DecodeBeatmap(stream); } - - if (beatmap == null || BeatmapSetInfo.StoryboardFile == null) - return beatmap; - - using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) - decoder.Decode(stream, beatmap); - - - return beatmap; } catch { @@ -624,12 +674,36 @@ namespace osu.Game.Beatmaps } protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); + + protected override Storyboard GetStoryboard() + { + if (BeatmapInfo?.Path == null && BeatmapSetInfo?.StoryboardFile == null) + return new Storyboard(); + + try + { + Decoder decoder; + using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo?.Path)))) + decoder = Decoder.GetDecoder(stream); + + // try for .osb first and fall back to .osu + string storyboardFile = BeatmapSetInfo.StoryboardFile ?? BeatmapInfo.Path; + using (var stream = new StreamReader(store.GetStream(getPathForFile(storyboardFile)))) + return decoder.GetStoryboardDecoder().DecodeStoryboard(stream); + } + catch + { + return new Storyboard(); + } + } } + public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null; + /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public void ImportFromStable() + public async Task ImportFromStable() { var stable = GetStableStorage?.Invoke(); @@ -639,7 +713,7 @@ namespace osu.Game.Beatmaps return; } - Import(stable.GetDirectories("Songs")); + await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning); } public void DeleteAll() @@ -651,6 +725,7 @@ namespace osu.Game.Beatmaps var notification = new ProgressNotification { Progress = 0, + CompletionText = "Deleted all beatmaps!", State = ProgressNotificationState.Active, }; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 89f9ebf47a..9010f922bb 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -9,9 +10,11 @@ using osu.Game.Users; namespace osu.Game.Beatmaps { - public class BeatmapMetadata + [Serializable] + public class BeatmapMetadata : IEquatable { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } private int? onlineBeatmapSetID; @@ -29,7 +32,10 @@ namespace osu.Game.Beatmaps public string Artist { get; set; } public string ArtistUnicode { get; set; } + [JsonIgnore] public List Beatmaps { get; set; } + + [JsonIgnore] public List BeatmapSets { get; set; } /// @@ -46,6 +52,7 @@ namespace osu.Game.Beatmaps /// /// The author of the beatmaps in this set. /// + [JsonIgnore] public User Author; public string Source { get; set; } @@ -56,6 +63,9 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } public string BackgroundFile { get; set; } + public override string ToString() => $"{Artist} - {Title} ({Author})"; + + [JsonIgnore] public string[] SearchableTerms => new[] { Author?.Username, @@ -66,5 +76,23 @@ namespace osu.Game.Beatmaps Source, Tags }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + public bool Equals(BeatmapMetadata other) + { + if (other == null) + return false; + + return onlineBeatmapSetID == other.onlineBeatmapSetID + && Title == other.Title + && TitleUnicode == other.TitleUnicode + && Artist == other.Artist + && ArtistUnicode == other.ArtistUnicode + && AuthorString == other.AuthorString + && Source == other.Source + && Tags == other.Tags + && PreviewTime == other.PreviewTime + && AudioFile == other.AudioFile + && BackgroundFile == other.BackgroundFile; + } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index c870c31a8b..a41beaab81 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -33,6 +33,8 @@ namespace osu.Game.Beatmaps public List Files { get; set; } + public override string ToString() => Metadata.ToString(); + public bool Protected { get; set; } } } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 3875202e32..fb45c17454 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -32,6 +32,18 @@ namespace osu.Game.Beatmaps { var context = GetContext(); + foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null)) + { + // If we detect a new metadata object it'll be attached to the current context so it can be reused + // to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local) + // of the corresponding table (.Set()) for matching entries to our criteria. + var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); + if (contextMetadata != null) + beatmap.Metadata = contextMetadata; + else + context.BeatmapMetadata.Attach(beatmap.Metadata); + } + context.BeatmapSetInfo.Attach(beatmapSet); context.SaveChanges(); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e46eb8e20a..f031ebe353 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -4,31 +4,37 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints { + [Serializable] public class ControlPointInfo { /// /// All timing points. /// - public readonly SortedList TimingPoints = new SortedList(Comparer.Default); + [JsonProperty] + public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); /// /// All difficulty points. /// - public readonly SortedList DifficultyPoints = new SortedList(Comparer.Default); + [JsonProperty] + public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); /// /// All sound points. /// - public readonly SortedList SoundPoints = new SortedList(Comparer.Default); + [JsonProperty] + public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); /// /// All effect points. /// - public readonly SortedList EffectPoints = new SortedList(Comparer.Default); + [JsonProperty] + public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); /// /// Finds the difficulty control point that is active at . @@ -49,7 +55,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the sound control point at. /// The sound control point. - public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time); + public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); /// /// Finds the timing control point that is active at . @@ -58,18 +64,21 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); + [JsonIgnore] /// /// Finds the maximum BPM represented by any timing control point. /// public double BPMMaximum => 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + [JsonIgnore] /// /// Finds the minimum BPM represented by any timing control point. /// public double BPMMinimum => 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + [JsonIgnore] /// /// Finds the mode BPM (most common BPM) represented by the control points. /// @@ -108,4 +117,4 @@ namespace osu.Game.Beatmaps.ControlPoints return list[index - 1]; } } -} \ No newline at end of file +} diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs new file mode 100644 index 0000000000..40e45da13c --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Audio; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class SampleControlPoint : ControlPoint + { + public const string DEFAULT_BANK = "normal"; + + /// + /// The default sample bank at this control point. + /// + public string SampleBank = DEFAULT_BANK; + + /// + /// The default sample volume at this control point. + /// + public int SampleVolume; + + /// + /// Create a SampleInfo based on the sample settings in this control point. + /// + /// The name of the same. + /// A populated . + public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo + { + Bank = SampleBank, + Name = sampleName, + Volume = SampleVolume, + }; + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs deleted file mode 100644 index 8084229382..0000000000 --- a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class SoundControlPoint : ControlPoint - { - /// - /// The default sample bank at this control point. - /// - public string SampleBank; - - /// - /// The default sample volume at this control point. - /// - public int SampleVolume; - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs deleted file mode 100644 index 163dd7fbe9..0000000000 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework; -using osu.Framework.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - public class BeatmapGroup : IStateful - { - public event Action StateChanged; - - public BeatmapPanel SelectedPanel; - - /// - /// Fires when one of our difficulties was selected. Will fire on first expand. - /// - public Action SelectionChanged; - - /// - /// Fires when one of our difficulties is clicked when already selected. Should start playing the map. - /// - public Action StartRequested; - - public Action DeleteRequested; - - public Action RestoreHiddenRequested; - - public Action HideDifficultyRequested; - - public Action EditRequested; - - public BeatmapSetHeader Header; - - public List BeatmapPanels; - - public BeatmapSetInfo BeatmapSet; - - private BeatmapGroupState state; - public BeatmapGroupState State - { - get { return state; } - set - { - state = value; - - switch (value) - { - case BeatmapGroupState.Expanded: - Header.State = PanelSelectedState.Selected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected; - break; - case BeatmapGroupState.Collapsed: - Header.State = PanelSelectedState.NotSelected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - case BeatmapGroupState.Hidden: - Header.State = PanelSelectedState.Hidden; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - } - - StateChanged?.Invoke(state); - } - } - - public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager) - { - if (beatmapSet == null) - throw new ArgumentNullException(nameof(beatmapSet)); - if (manager == null) - throw new ArgumentNullException(nameof(manager)); - - BeatmapSet = beatmapSet; - WorkingBeatmap beatmap = manager.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault()); - - Header = new BeatmapSetHeader(beatmap) - { - GainedSelection = headerGainedSelection, - DeleteRequested = b => DeleteRequested(b), - RestoreHiddenRequested = b => RestoreHiddenRequested(b), - RelativeSizeAxes = Axes.X, - }; - - BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b) - { - Alpha = 0, - GainedSelection = panelGainedSelection, - HideRequested = p => HideDifficultyRequested?.Invoke(p), - StartRequested = p => StartRequested?.Invoke(p.Beatmap), - EditRequested = p => EditRequested?.Invoke(p.Beatmap), - RelativeSizeAxes = Axes.X, - }).ToList(); - - Header.AddDifficultyIcons(BeatmapPanels); - } - - - private void headerGainedSelection(BeatmapSetHeader panel) - { - State = BeatmapGroupState.Expanded; - - //we want to make sure one of our children is selected in the case none have been selected yet. - if (SelectedPanel == null) - BeatmapPanels.First().State = PanelSelectedState.Selected; - } - - private void panelGainedSelection(BeatmapPanel panel) - { - try - { - if (SelectedPanel == panel) return; - - if (SelectedPanel != null) - SelectedPanel.State = PanelSelectedState.NotSelected; - SelectedPanel = panel; - } - finally - { - State = BeatmapGroupState.Expanded; - SelectionChanged?.Invoke(this, SelectedPanel); - } - } - } - - public enum BeatmapGroupState - { - Collapsed, - Expanded, - Hidden, - } -} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 614ebc236b..ba79db3f48 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -11,21 +11,44 @@ namespace osu.Game.Beatmaps.Drawables public class BeatmapSetCover : Sprite { private readonly BeatmapSetInfo set; - public BeatmapSetCover(BeatmapSetInfo set) + private readonly BeatmapSetCoverType type; + + public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) { if (set == null) throw new ArgumentNullException(nameof(set)); this.set = set; + this.type = type; } [BackgroundDependencyLoader] private void load(TextureStore textures) { - string resource = set.OnlineInfo.Covers.Cover; + string resource = null; + + switch (type) + { + case BeatmapSetCoverType.Cover: + resource = set.OnlineInfo.Covers.Cover; + break; + case BeatmapSetCoverType.Card: + resource = set.OnlineInfo.Covers.Card; + break; + case BeatmapSetCoverType.List: + resource = set.OnlineInfo.Covers.List; + break; + } if (resource != null) Texture = textures.Get(resource); } } + + public enum BeatmapSetCoverType + { + Cover, + Card, + List, + } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index c187aa592a..3ec83ed8d5 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -21,8 +21,7 @@ namespace osu.Game.Beatmaps Metadata = new BeatmapMetadata { Artist = "please load a beatmap!", - Title = "no beatmaps available!", - AuthorString = "no one", + Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), BaseDifficulty = new BeatmapDifficulty @@ -63,6 +62,8 @@ namespace osu.Game.Beatmaps public override string Description => "dummy"; + public override string ShortName => "dummy"; + public DummyRuleset(RulesetInfo rulesetInfo) : base(rulesetInfo) { diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs deleted file mode 100644 index 7e1a87085c..0000000000 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; - -namespace osu.Game.Beatmaps.Formats -{ - public abstract class BeatmapDecoder - { - private static readonly Dictionary decoders = new Dictionary(); - - static BeatmapDecoder() - { - OsuLegacyDecoder.Register(); - } - - public static BeatmapDecoder GetDecoder(StreamReader stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - string line; - do { line = stream.ReadLine()?.Trim(); } - while (line != null && line.Length == 0); - - if (line == null || !decoders.ContainsKey(line)) - throw new IOException(@"Unknown file format"); - return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line); - } - - protected static void AddDecoder(string magic) where T : BeatmapDecoder - { - decoders[magic] = typeof(T); - } - - public virtual Beatmap Decode(StreamReader stream) - { - return ParseFile(stream); - } - - public virtual void Decode(StreamReader stream, Beatmap beatmap) - { - ParseFile(stream, beatmap); - } - - protected virtual Beatmap ParseFile(StreamReader stream) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata(), - BaseDifficulty = new BeatmapDifficulty(), - }, - }; - - ParseFile(stream, beatmap); - return beatmap; - } - - protected abstract void ParseFile(StreamReader stream, Beatmap beatmap); - } -} diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs new file mode 100644 index 0000000000..a6e2699262 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps.Formats +{ + public abstract class Decoder + { + private static readonly Dictionary> decoders = new Dictionary>(); + + static Decoder() + { + LegacyDecoder.Register(); + JsonBeatmapDecoder.Register(); + } + + /// + /// Retrieves a to parse a . + /// + /// A stream pointing to the . + public static Decoder GetDecoder(StreamReader stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + string line; + do + { line = stream.ReadLine()?.Trim(); } + while (line != null && line.Length == 0); + + if (line == null || !decoders.ContainsKey(line)) + throw new IOException(@"Unknown file format"); + + return decoders[line](line); + } + + /// + /// Registers an instantiation function for a . + /// + /// 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) + { + decoders[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(StreamReader stream) + { + var storyboard = new Storyboard(); + 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 new file mode 100644 index 0000000000..29e7cee336 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using osu.Game.IO.Serialization; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps.Formats +{ + public class JsonBeatmapDecoder : Decoder + { + public static void Register() + { + AddDecoder("{", m => new JsonBeatmapDecoder()); + } + + public override Decoder GetStoryboardDecoder() => this; + + protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) + { + stream.BaseStream.Position = 0; + stream.DiscardBufferedData(); + + stream.ReadToEnd().DeserializeInto(beatmap); + + 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 new file mode 100644 index 0000000000..ea29e480ec --- /dev/null +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -0,0 +1,421 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using System.IO; +using OpenTK.Graphics; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Beatmaps.ControlPoints; +using System.Collections.Generic; + +namespace osu.Game.Beatmaps.Formats +{ + public class LegacyBeatmapDecoder : LegacyDecoder + { + private Beatmap beatmap; + + private bool hasCustomColours; + private ConvertHitObjectParser parser; + + private LegacySampleBank defaultSampleBank; + private int defaultSampleVolume = 100; + + public LegacyBeatmapDecoder() + { + } + + public LegacyBeatmapDecoder(string header) + { + BeatmapVersion = int.Parse(header.Substring(17)); + } + + protected override void ParseBeatmap(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; + + ParseContent(stream); + + foreach (var hitObject in this.beatmap.HitObjects) + 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 void ProcessSection(Section section, string line) + { + switch (section) + { + case Section.General: + handleGeneral(line); + break; + case Section.Editor: + handleEditor(line); + break; + case Section.Metadata: + handleMetadata(line); + break; + case Section.Difficulty: + handleDifficulty(line); + break; + case Section.Events: + handleEvents(line); + break; + case Section.TimingPoints: + handleTimingPoints(line); + break; + case Section.Colours: + handleColours(line); + break; + case Section.HitObjects: + handleHitObjects(line); + break; + case Section.Variables: + handleVariables(line); + break; + } + } + + private void handleGeneral(string line) + { + var pair = splitKeyVal(line, ':'); + + var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) + { + case @"AudioFilename": + metadata.AudioFile = pair.Value; + break; + case @"AudioLeadIn": + beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); + break; + case @"PreviewTime": + metadata.PreviewTime = int.Parse(pair.Value); + break; + case @"Countdown": + beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; + break; + case @"SampleSet": + defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); + break; + case @"SampleVolume": + defaultSampleVolume = int.Parse(pair.Value); + break; + case @"StackLeniency": + beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"Mode": + beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + + switch (beatmap.BeatmapInfo.RulesetID) + { + case 0: + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + break; + case 1: + parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); + break; + case 2: + parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); + break; + case 3: + parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); + break; + } + break; + case @"LetterboxInBreaks": + beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; + break; + case @"SpecialStyle": + beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; + break; + case @"WidescreenStoryboard": + beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; + break; + } + } + + private void handleEditor(string line) + { + var pair = splitKeyVal(line, ':'); + + switch (pair.Key) + { + case @"Bookmarks": + beatmap.BeatmapInfo.StoredBookmarks = pair.Value; + break; + case @"DistanceSpacing": + beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"BeatDivisor": + beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); + break; + case @"GridSize": + beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); + break; + case @"TimelineZoom": + beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + } + } + + private void handleMetadata(string line) + { + var pair = splitKeyVal(line, ':'); + + var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) + { + case @"Title": + metadata.Title = pair.Value; + break; + case @"TitleUnicode": + metadata.TitleUnicode = pair.Value; + break; + case @"Artist": + metadata.Artist = pair.Value; + break; + case @"ArtistUnicode": + metadata.ArtistUnicode = pair.Value; + break; + case @"Creator": + metadata.AuthorString = pair.Value; + break; + case @"Version": + beatmap.BeatmapInfo.Version = pair.Value; + break; + case @"Source": + beatmap.BeatmapInfo.Metadata.Source = pair.Value; + break; + case @"Tags": + beatmap.BeatmapInfo.Metadata.Tags = pair.Value; + break; + case @"BeatmapID": + beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + break; + case @"BeatmapSetID": + beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); + metadata.OnlineBeatmapSetID = int.Parse(pair.Value); + break; + } + } + + private void handleDifficulty(string line) + { + var pair = splitKeyVal(line, ':'); + + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) + { + case @"HPDrainRate": + difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"CircleSize": + difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"OverallDifficulty": + difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"ApproachRate": + difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"SliderMultiplier": + difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"SliderTickRate": + difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + } + } + + private void handleEvents(string line) + { + DecodeVariables(ref line); + + string[] split = line.Split(','); + + EventType type; + if (!Enum.TryParse(split[0], out type)) + throw new InvalidDataException($@"Unknown event type {split[0]}"); + + switch (type) + { + case EventType.Background: + string filename = split[2].Trim('"'); + beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + break; + case EventType.Break: + var breakEvent = new BreakPeriod + { + StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), + EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) + }; + + if (!breakEvent.HasEffect) + return; + + beatmap.Breaks.Add(breakEvent); + break; + } + } + + private void handleTimingPoints(string line) + { + string[] split = line.Split(','); + + double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); + double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; + + TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + if (split.Length >= 3) + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + + LegacySampleBank sampleSet = defaultSampleBank; + if (split.Length >= 4) + sampleSet = (LegacySampleBank)int.Parse(split[3]); + + //SampleBank sampleBank = SampleBank.Default; + //if (split.Length >= 5) + // sampleBank = (SampleBank)int.Parse(split[4]); + + int sampleVolume = defaultSampleVolume; + if (split.Length >= 6) + sampleVolume = int.Parse(split[5]); + + bool timingChange = true; + if (split.Length >= 7) + timingChange = split[6][0] == '1'; + + bool kiaiMode = false; + bool omitFirstBarSignature = false; + if (split.Length >= 8) + { + int effectFlags = int.Parse(split[7]); + kiaiMode = (effectFlags & 1) > 0; + omitFirstBarSignature = (effectFlags & 8) > 0; + } + + string stringSampleSet = sampleSet.ToString().ToLower(); + if (stringSampleSet == @"none") + stringSampleSet = @"normal"; + + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); + SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); + + if (timingChange) + { + beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint + { + Time = time, + BeatLength = beatLength, + TimeSignature = timeSignature + }); + } + + if (speedMultiplier != difficultyPoint.SpeedMultiplier) + { + beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); + beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint + { + Time = time, + SpeedMultiplier = speedMultiplier + }); + } + + if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) + { + beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint + { + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume + }); + } + + if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) + { + beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint + { + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature + }); + } + } + + private void handleColours(string line) + { + var pair = splitKeyVal(line, ':'); + + string[] split = pair.Value.Split(','); + + if (split.Length != 3) + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); + + byte r, g, b; + if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) + throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); + + if (!hasCustomColours) + { + beatmap.ComboColors.Clear(); + hasCustomColours = true; + } + + // Note: the combo index specified in the beatmap is discarded + if (pair.Key.StartsWith(@"Combo")) + { + beatmap.ComboColors.Add(new Color4 + { + R = r / 255f, + G = g / 255f, + B = b / 255f, + A = 1f, + }); + } + } + + private void handleHitObjects(string line) + { + // If the ruleset wasn't specified, assume the osu!standard ruleset. + if (parser == null) + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + + var obj = parser.Parse(line); + + if (obj != null) + beatmap.HitObjects.Add(obj); + } + + private void handleVariables(string line) + { + var pair = splitKeyVal(line, '='); + Variables[pair.Key] = pair.Value; + } + + private KeyValuePair splitKeyVal(string line, char separator) + { + var split = line.Trim().Split(new[] { separator }, 2); + + return new KeyValuePair + ( + split[0].Trim(), + split.Length > 1 ? split[1].Trim() : string.Empty + ); + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs new file mode 100644 index 0000000000..e5ced5f772 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -0,0 +1,163 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +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 static void Register() + { + 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 + } + + protected int BeatmapVersion; + protected readonly Dictionary Variables = new Dictionary(); + + 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) + { + Section section = Section.None; + + string line; + while ((line = stream.ReadLine()) != null) + { + 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)) + throw new InvalidDataException($@"Unknown osu section {line}"); + continue; + } + + ProcessSection(section, line); + } + } + + protected virtual bool ShouldSkipLine(string line) + { + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//")) + return true; + return false; + } + + protected abstract void ProcessSection(Section section, string line); + + /// + /// Decodes any beatmap variables present in a line into their real values. + /// + /// The line which may contains variables. + protected void DecodeVariables(ref string line) + { + while (line.IndexOf('$') >= 0) + { + string origLine = line; + string[] split = line.Split(','); + for (int i = 0; i < split.Length; i++) + { + var item = split[i]; + if (item.StartsWith("$") && Variables.ContainsKey(item)) + split[i] = Variables[item]; + } + + line = string.Join(",", split); + if (line == origLine) + break; + } + } + + protected enum Section + { + None, + General, + Editor, + Metadata, + Difficulty, + Events, + TimingPoints, + Colours, + HitObjects, + Variables, + } + + internal enum LegacySampleBank + { + None = 0, + Normal = 1, + Soft = 2, + Drum = 3 + } + + internal enum EventType + { + Background = 0, + Video = 1, + Break = 2, + Colour = 3, + Sprite = 4, + Sample = 5, + Animation = 6 + } + + internal enum LegacyOrigins + { + TopLeft, + Centre, + CentreLeft, + TopRight, + BottomCentre, + TopCentre, + Custom, + CentreRight, + BottomLeft, + BottomRight + }; + + internal enum StoryLayer + { + Background = 0, + Fail = 1, + Pass = 2, + Foreground = 3 + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs new file mode 100644 index 0000000000..8da6a0cefb --- /dev/null +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -0,0 +1,271 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using System.IO; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.IO.File; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps.Formats +{ + public class LegacyStoryboardDecoder : LegacyDecoder + { + private Storyboard storyboard; + + private StoryboardSprite storyboardSprite; + private CommandTimelineGroup timelineGroup; + + public LegacyStoryboardDecoder() + { + } + + public LegacyStoryboardDecoder(int beatmapVersion) + { + BeatmapVersion = beatmapVersion; + } + + protected override void ParseStoryboard(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); + } + + protected override void ProcessSection(Section section, string line) + { + switch (section) + { + case Section.Events: + handleEvents(line); + break; + } + } + + private void handleEvents(string line) + { + var depth = 0; + while (line.StartsWith(" ") || line.StartsWith("_")) + { + ++depth; + line = line.Substring(1); + } + + DecodeVariables(ref line); + + string[] split = line.Split(','); + + if (depth == 0) + { + storyboardSprite = null; + + EventType type; + if (!Enum.TryParse(split[0], out type)) + throw new InvalidDataException($@"Unknown event type {split[0]}"); + + 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); + } + 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); + } + 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)); + } + break; + } + } + else + { + if (depth < 2) + timelineGroup = storyboardSprite?.TimelineGroup; + + var commandType = split[0]; + 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); + } + break; + case "L": + { + 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) + { + 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}"); + } + } + break; + } + } + } + + private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString(); + + private Anchor parseOrigin(string value) + { + var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) + { + case LegacyOrigins.TopLeft: + return Anchor.TopLeft; + case LegacyOrigins.TopCentre: + return Anchor.TopCentre; + case LegacyOrigins.TopRight: + return Anchor.TopRight; + case LegacyOrigins.CentreLeft: + return Anchor.CentreLeft; + case LegacyOrigins.Centre: + return Anchor.Centre; + case LegacyOrigins.CentreRight: + return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: + return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: + return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: + return Anchor.BottomRight; + } + throw new InvalidDataException($@"Unknown origin: {value}"); + } + + private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"')); + } +} diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs deleted file mode 100644 index 11631e9447..0000000000 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ /dev/null @@ -1,781 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using OpenTK.Graphics; -using osu.Game.Beatmaps.Timing; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Storyboards; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.IO.File; - -namespace osu.Game.Beatmaps.Formats -{ - public class OsuLegacyDecoder : BeatmapDecoder - { - public static void Register() - { - AddDecoder(@"osu file format v14"); - AddDecoder(@"osu file format v13"); - AddDecoder(@"osu file format v12"); - AddDecoder(@"osu file format v11"); - AddDecoder(@"osu file format v10"); - AddDecoder(@"osu file format v9"); - AddDecoder(@"osu file format v8"); - AddDecoder(@"osu file format v7"); - AddDecoder(@"osu file format v6"); - AddDecoder(@"osu file format v5"); - AddDecoder(@"osu file format v4"); - AddDecoder(@"osu file format v3"); - // TODO: differences between versions - } - - private ConvertHitObjectParser parser; - - private readonly Dictionary variables = new Dictionary(); - - private LegacySampleBank defaultSampleBank; - private int defaultSampleVolume = 100; - - private readonly int beatmapVersion; - - public OsuLegacyDecoder() - { - } - - public OsuLegacyDecoder(string header) - { - beatmapVersion = int.Parse(header.Substring(17)); - } - - private enum Section - { - None, - General, - Editor, - Metadata, - Difficulty, - Events, - TimingPoints, - Colours, - HitObjects, - Variables, - } - - private void handleGeneral(Beatmap beatmap, string line) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, ':'); - - var metadata = beatmap.BeatmapInfo.Metadata; - switch (pair.Key) - { - case @"AudioFilename": - metadata.AudioFile = pair.Value; - break; - case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); - break; - case @"PreviewTime": - metadata.PreviewTime = int.Parse(pair.Value); - break; - case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; - break; - case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); - break; - case @"SampleVolume": - defaultSampleVolume = int.Parse(pair.Value); - break; - case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); - - switch (beatmap.BeatmapInfo.RulesetID) - { - case 0: - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); - break; - case 1: - parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); - break; - case 2: - parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); - break; - case 3: - parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); - break; - } - break; - case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; - break; - case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; - break; - case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; - break; - } - } - - private void handleEditor(Beatmap beatmap, string line) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, ':'); - - switch (pair.Key) - { - case @"Bookmarks": - beatmap.BeatmapInfo.StoredBookmarks = pair.Value; - break; - case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); - break; - case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); - break; - case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - } - } - - private void handleMetadata(Beatmap beatmap, string line) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, ':'); - - var metadata = beatmap.BeatmapInfo.Metadata; - switch (pair.Key) - { - case @"Title": - metadata.Title = pair.Value; - break; - case @"TitleUnicode": - metadata.TitleUnicode = pair.Value; - break; - case @"Artist": - metadata.Artist = pair.Value; - break; - case @"ArtistUnicode": - metadata.ArtistUnicode = pair.Value; - break; - case @"Creator": - metadata.AuthorString = pair.Value; - break; - case @"Version": - beatmap.BeatmapInfo.Version = pair.Value; - break; - case @"Source": - beatmap.BeatmapInfo.Metadata.Source = pair.Value; - break; - case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = pair.Value; - break; - case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); - break; - case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); - metadata.OnlineBeatmapSetID = int.Parse(pair.Value); - break; - } - } - - private void handleDifficulty(Beatmap beatmap, string line) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, ':'); - - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; - switch (pair.Key) - { - case @"HPDrainRate": - difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"CircleSize": - difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"ApproachRate": - difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"SliderMultiplier": - difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"SliderTickRate": - difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - } - } - - /// - /// Decodes any beatmap variables present in a line into their real values. - /// - /// The line which may contains variables. - private void decodeVariables(ref string line) - { - if (line == null) - throw new ArgumentNullException(nameof(line)); - - while (line.IndexOf('$') >= 0) - { - string origLine = line; - string[] split = line.Split(','); - for (int i = 0; i < split.Length; i++) - { - var item = split[i]; - if (item.StartsWith("$") && variables.ContainsKey(item)) - split[i] = variables[item]; - } - - line = string.Join(",", split); - if (line == origLine) break; - } - } - - private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup) - { - if (line == null) - throw new ArgumentNullException(nameof(line)); - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - var depth = 0; - while (line.StartsWith(" ") || line.StartsWith("_")) - { - ++depth; - line = line.Substring(1); - } - - decodeVariables(ref line); - - string[] split = line.Split(','); - - if (depth == 0) - { - storyboardSprite = null; - - EventType type; - if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); - - switch (type) - { - case EventType.Video: - case EventType.Background: - string filename = split[2].Trim('"'); - - if (type == EventType.Background) - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; - - break; - case EventType.Break: - var breakEvent = new BreakPeriod - { - StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), - EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) - }; - - if (!breakEvent.HasEffect) - return; - - beatmap.Breaks.Add(breakEvent); - break; - 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)); - beatmap.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); - beatmap.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; - beatmap.Storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } - break; - } - } - else - { - if (depth < 2) - timelineGroup = storyboardSprite?.TimelineGroup; - - var commandType = split[0]; - 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); - } - break; - case "L": - { - 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) - { - 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}"); - } - } - break; - } - } - } - - private static string cleanFilename(string path) - => FileSafety.PathStandardise(path.Trim('\"')); - - private static Anchor parseOrigin(string value) - { - var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); - switch (origin) - { - case LegacyOrigins.TopLeft: return Anchor.TopLeft; - case LegacyOrigins.TopCentre: return Anchor.TopCentre; - case LegacyOrigins.TopRight: return Anchor.TopRight; - case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; - case LegacyOrigins.Centre: return Anchor.Centre; - case LegacyOrigins.CentreRight: return Anchor.CentreRight; - case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; - case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; - case LegacyOrigins.BottomRight: return Anchor.BottomRight; - } - throw new InvalidDataException($@"Unknown origin: {value}"); - } - - private static string parseLayer(string value) - => Enum.Parse(typeof(StoryLayer), value).ToString(); - - private void handleTimingPoints(Beatmap beatmap, string line) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - string[] split = line.Split(','); - - double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); - double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; - if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); - - LegacySampleBank sampleSet = defaultSampleBank; - if (split.Length >= 4) - sampleSet = (LegacySampleBank)int.Parse(split[3]); - - //SampleBank sampleBank = SampleBank.Default; - //if (split.Length >= 5) - // sampleBank = (SampleBank)int.Parse(split[4]); - - int sampleVolume = defaultSampleVolume; - if (split.Length >= 6) - sampleVolume = int.Parse(split[5]); - - bool timingChange = true; - if (split.Length >= 7) - timingChange = split[6][0] == '1'; - - bool kiaiMode = false; - bool omitFirstBarSignature = false; - if (split.Length >= 8) - { - int effectFlags = int.Parse(split[7]); - kiaiMode = (effectFlags & 1) > 0; - omitFirstBarSignature = (effectFlags & 8) > 0; - } - - string stringSampleSet = sampleSet.ToString().ToLower(); - if (stringSampleSet == @"none") - stringSampleSet = @"normal"; - - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); - SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); - - if (timingChange) - { - beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint - { - Time = time, - BeatLength = beatLength, - TimeSignature = timeSignature - }); - } - - if (speedMultiplier != difficultyPoint.SpeedMultiplier) - { - beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); - beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint - { - Time = time, - SpeedMultiplier = speedMultiplier - }); - } - - if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume) - { - beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint - { - Time = time, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume - }); - } - - if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) - { - beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint - { - Time = time, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature - }); - } - } - - private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, ':'); - - string[] split = pair.Value.Split(','); - - if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); - - byte r, g, b; - if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) - throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); - - if (!hasCustomColours) - { - beatmap.ComboColors.Clear(); - hasCustomColours = true; - } - - // Note: the combo index specified in the beatmap is discarded - if (pair.Key.StartsWith(@"Combo")) - { - beatmap.ComboColors.Add(new Color4 - { - R = r / 255f, - G = g / 255f, - B = b / 255f, - A = 1f, - }); - } - } - - private void handleVariables(string line) - { - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var pair = splitKeyVal(line, '='); - variables[pair.Key] = pair.Value; - } - - protected override Beatmap ParseFile(StreamReader stream) - { - return new LegacyBeatmap(base.ParseFile(stream)); - } - - public override Beatmap Decode(StreamReader stream) - { - return new LegacyBeatmap(base.Decode(stream)); - } - - protected override void ParseFile(StreamReader stream, Beatmap beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion; - - Section section = Section.None; - bool hasCustomColours = false; - StoryboardSprite storyboardSprite = null; - CommandTimelineGroup timelineGroup = null; - - string line; - while ((line = stream.ReadLine()) != null) - { - if (string.IsNullOrWhiteSpace(line)) - continue; - - if (line.StartsWith("//")) - continue; - - 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)) - throw new InvalidDataException($@"Unknown osu section {line}"); - continue; - } - - switch (section) - { - case Section.General: - handleGeneral(beatmap, line); - break; - case Section.Editor: - handleEditor(beatmap, line); - break; - case Section.Metadata: - handleMetadata(beatmap, line); - break; - case Section.Difficulty: - handleDifficulty(beatmap, line); - break; - case Section.Events: - handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup); - break; - case Section.TimingPoints: - handleTimingPoints(beatmap, line); - break; - case Section.Colours: - handleColours(beatmap, line, ref hasCustomColours); - break; - case Section.HitObjects: - - // If the ruleset wasn't specified, assume the osu!standard ruleset. - if (parser == null) - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); - - var obj = parser.Parse(line); - - if (obj != null) - beatmap.HitObjects.Add(obj); - - break; - case Section.Variables: - handleVariables(line); - break; - } - } - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - } - - private KeyValuePair splitKeyVal(string line, char separator) - { - if (line == null) - throw new ArgumentNullException(nameof(line)); - - var split = line.Trim().Split(new[] { separator }, 2); - - return new KeyValuePair - ( - split[0].Trim(), - split.Length > 1 ? split[1].Trim() : string.Empty - ); - } - - internal enum LegacySampleBank - { - None = 0, - Normal = 1, - Soft = 2, - Drum = 3 - } - - internal enum EventType - { - Background = 0, - Video = 1, - Break = 2, - Colour = 3, - Sprite = 4, - Sample = 5, - Animation = 6 - } - - internal enum LegacyOrigins - { - TopLeft, - Centre, - CentreLeft, - TopRight, - BottomCentre, - TopCentre, - Custom, - CentreRight, - BottomLeft, - BottomRight - }; - - internal enum StoryLayer - { - Background = 0, - Fail = 1, - Pass = 2, - Foreground = 3 - } - } -} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 2a8178882e..8c8cf212c1 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -9,6 +9,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Game.Storyboards; +using osu.Framework.IO.File; +using System.IO; +using osu.Game.IO.Serialization; +using System.Diagnostics; namespace osu.Game.Beatmaps { @@ -31,17 +36,30 @@ namespace osu.Game.Beatmaps Mods.ValueChanged += mods => applyRateAdjustments(); beatmap = new AsyncLazy(populateBeatmap); - background = new AsyncLazy(populateBackground); + background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed); track = new AsyncLazy(populateTrack); waveform = new AsyncLazy(populateWaveform); + storyboard = new AsyncLazy(populateStoryboard); + } + + /// + /// Saves the . + /// + public void Save() + { + var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); + using (var sw = new StreamWriter(path)) + sw.WriteLine(Beatmap.Serialize()); + Process.Start(path); } protected abstract Beatmap GetBeatmap(); protected abstract Texture GetBackground(); protected abstract Track GetTrack(); protected virtual Waveform GetWaveform() => new Waveform(); + protected virtual Storyboard GetStoryboard() => new Storyboard(); - public bool BeatmapLoaded => beatmap.IsValueCreated; + public bool BeatmapLoaded => beatmap.IsResultAvailable; public Beatmap Beatmap => beatmap.Value.Result; public async Task GetBeatmapAsync() => await beatmap.Value; @@ -57,14 +75,14 @@ namespace osu.Game.Beatmaps return b; } - public bool BackgroundLoaded => background.IsValueCreated; + public bool BackgroundLoaded => background.IsResultAvailable; public Texture Background => background.Value.Result; public async Task GetBackgroundAsync() => await background.Value; private AsyncLazy background; private Texture populateBackground() => GetBackground(); - public bool TrackLoaded => track.IsValueCreated; + public bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value.Result; public async Task GetTrackAsync() => await track.Value; private AsyncLazy track; @@ -77,19 +95,26 @@ namespace osu.Game.Beatmaps return t; } - public bool WaveformLoaded => waveform.IsValueCreated; + public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value.Result; public async Task GetWaveformAsync() => await waveform.Value; private readonly AsyncLazy waveform; private Waveform populateWaveform() => GetWaveform(); + public bool StoryboardLoaded => storyboard.IsResultAvailable; + public Storyboard Storyboard => storyboard.Value.Result; + public async Task GetStoryboardAsync() => await storyboard.Value; + private readonly AsyncLazy storyboard; + + private Storyboard populateStoryboard() => GetStoryboard(); + public void TransferTo(WorkingBeatmap other) { - if (track.IsValueCreated && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) + if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) other.track = track; - if (background.IsValueCreated && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) + if (background.IsResultAvailable && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) other.background = background; } @@ -97,16 +122,18 @@ namespace osu.Game.Beatmaps { if (BackgroundLoaded) Background?.Dispose(); if (WaveformLoaded) Waveform?.Dispose(); + if (StoryboardLoaded) Storyboard?.Dispose(); } - public void DisposeTrack() - { - if (TrackLoaded) Track?.Dispose(); - } + /// + /// Eagerly dispose of the audio track associated with this (if any). + /// Accessing track again will load a fresh instance. + /// + public void RecycleTrack() => track.Recycle(); private void applyRateAdjustments(Track t = null) { - if (t == null && track.IsValueCreated) t = Track; + if (t == null && track.IsResultAvailable) t = Track; if (t == null) return; t.ResetSpeedAdjustments(); @@ -114,12 +141,65 @@ namespace osu.Game.Beatmaps mod.ApplyToClock(t); } - public class AsyncLazy : Lazy> + public class AsyncLazy { - public AsyncLazy(Func valueFactory) - : base(() => Task.Run(valueFactory)) + private Lazy> lazy; + private readonly Func valueFactory; + private readonly Func stillValidFunction; + + private readonly object initLock = new object(); + + public AsyncLazy(Func valueFactory, Func stillValidFunction = null) { + this.valueFactory = valueFactory; + this.stillValidFunction = stillValidFunction; + + recreate(); } + + public void Recycle() + { + if (!IsResultAvailable) return; + + (lazy.Value.Result as IDisposable)?.Dispose(); + recreate(); + } + + public bool IsResultAvailable + { + get + { + recreateIfInvalid(); + return lazy.Value.IsCompleted; + } + } + + public Task Value + { + get + { + recreateIfInvalid(); + return lazy.Value; + } + } + + private void recreateIfInvalid() + { + lock (initLock) + { + if (!lazy.IsValueCreated || !lazy.Value.IsCompleted) + // we have not yet been initialised or haven't run the task. + return; + + if (stillValidFunction?.Invoke(lazy.Value.Result) ?? true) + // we are still in a valid state. + return; + + recreate(); + } + } + + private void recreate() => lazy = new Lazy>(() => Task.Run(valueFactory)); } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1a7d29e907..f4c7bdb586 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Configuration Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); - Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); + Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); @@ -108,7 +108,7 @@ namespace osu.Game.Configuration SaveUsername, DisplayStarsMinimum, DisplayStarsMaximum, - SelectionRandomType, + RandomSelectAlgorithm, SnakingInSliders, SnakingOutSliders, ShowFpsDisplay, diff --git a/osu.Game/Configuration/SelectionRandomType.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs similarity index 86% rename from osu.Game/Configuration/SelectionRandomType.cs rename to osu.Game/Configuration/RandomSelectAlgorithm.cs index 298ee71e36..cde657dba8 100644 --- a/osu.Game/Configuration/SelectionRandomType.cs +++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs @@ -5,11 +5,11 @@ using System.ComponentModel; namespace osu.Game.Configuration { - public enum SelectionRandomType + public enum RandomSelectAlgorithm { [Description("Never repeat")] RandomPermutation, [Description("Random")] Random } -} \ No newline at end of file +} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 0caff1e98d..f9509a279e 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -87,6 +87,7 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.ReferenceCount); modelBuilder.Entity().HasIndex(b => b.Available); + modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); modelBuilder.Entity().HasOne(b => b.BaseDifficulty); } diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index e752b1d91a..326aa2fb79 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -15,14 +15,19 @@ namespace osu.Game.Graphics { public class SpriteIcon : CompositeDrawable { - private readonly Sprite spriteShadow; - private readonly Sprite spriteMain; + private Sprite spriteShadow; + private Sprite spriteMain; private Cached layout = new Cached(); - private readonly Container shadowVisibility; + private Container shadowVisibility; - public SpriteIcon() + private FontStore store; + + [BackgroundDependencyLoader] + private void load(FontStore store) { + this.store = store; + InternalChildren = new Drawable[] { shadowVisibility = new Container @@ -39,7 +44,7 @@ namespace osu.Game.Graphics Y = 2, Colour = new Color4(0f, 0f, 0f, 0.2f), }, - Alpha = 0, + Alpha = shadow ? 1 : 0, }, spriteMain = new Sprite { @@ -49,14 +54,7 @@ namespace osu.Game.Graphics FillMode = FillMode.Fit }, }; - } - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; updateTexture(); } @@ -105,12 +103,15 @@ namespace osu.Game.Graphics } } + private bool shadow; public bool Shadow { - get { return spriteShadow.IsPresent; } + get { return shadow; } set { - shadowVisibility.Alpha = value ? 1 : 0; + shadow = value; + if (shadowVisibility != null) + shadowVisibility.Alpha = value ? 1 : 0; } } diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index bb62815a7b..f07bc4114f 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -12,6 +12,8 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; +using osu.Framework.Configuration; +using osu.Framework.Input; namespace osu.Game.Graphics.UserInterface { @@ -22,62 +24,7 @@ namespace osu.Game.Graphics.UserInterface private const float glow_fade_duration = 250; private const float click_duration = 200; - private Color4 buttonColour; - public Color4 ButtonColour - { - get - { - return buttonColour; - } - set - { - buttonColour = value; - updateGlow(); - colourContainer.Colour = value; - } - } - - private Color4 backgroundColour = OsuColour.Gray(34); - public Color4 BackgroundColour - { - get - { - return backgroundColour; - } - set - { - backgroundColour = value; - background.Colour = value; - } - } - - private string text; - public string Text - { - get - { - return text; - } - set - { - text = value; - spriteText.Text = Text; - } - } - - private float textSize = 28; - public float TextSize - { - get - { - return textSize; - } - set - { - textSize = value; - spriteText.TextSize = value; - } - } + public readonly BindableBool Selected = new BindableBool(); private readonly Container backgroundContainer; private readonly Container colourContainer; @@ -89,71 +36,6 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText spriteText; private Vector2 hoverSpacing => new Vector2(3f, 0f); - private bool didClick; // Used for making sure that the OnMouseDown animation can call instead of OnHoverLost's when clicking - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); - - protected override bool OnClick(Framework.Input.InputState state) - { - didClick = true; - colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); - flash(); - - this.Delay(click_duration).Schedule(delegate - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f)); - spriteText.Spacing = Vector2.Zero; - glowContainer.FadeOut(); - }); - - return base.OnClick(state); - } - - protected override bool OnHover(Framework.Input.InputState state) - { - spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - - colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); - glowContainer.FadeIn(glow_fade_duration, Easing.Out); - base.OnHover(state); - return true; - } - - protected override void OnHoverLost(Framework.Input.InputState state) - { - if (!didClick) - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); - spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(glow_fade_duration, Easing.Out); - } - - didClick = false; - } - - private void flash() - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both - }; - - colourContainer.Add(flash); - - flash.Colour = ButtonColour; - flash.Blending = BlendingMode.Additive; - flash.Alpha = 0.3f; - flash.FadeOutFromOne(click_duration); - flash.Expire(); - } - - private void updateGlow() - { - leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); - centerGlow.Colour = ButtonColour; - rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); - } - public DialogButton() { RelativeSizeAxes = Axes.X; @@ -268,6 +150,135 @@ namespace osu.Game.Graphics.UserInterface }; updateGlow(); + + Selected.ValueChanged += selectionChanged; + } + + private Color4 buttonColour; + public Color4 ButtonColour + { + get + { + return buttonColour; + } + set + { + buttonColour = value; + updateGlow(); + colourContainer.Colour = value; + } + } + + private Color4 backgroundColour = OsuColour.Gray(34); + public Color4 BackgroundColour + { + get + { + return backgroundColour; + } + set + { + backgroundColour = value; + background.Colour = value; + } + } + + private string text; + public string Text + { + get + { + return text; + } + set + { + text = value; + spriteText.Text = Text; + } + } + + private float textSize = 28; + public float TextSize + { + get + { + return textSize; + } + set + { + textSize = value; + spriteText.TextSize = value; + } + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnClick(InputState state) + { + colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); + flash(); + + this.Delay(click_duration).Schedule(delegate + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f)); + spriteText.Spacing = Vector2.Zero; + glowContainer.FadeOut(); + }); + + return base.OnClick(state); + } + + protected override bool OnHover(InputState state) + { + base.OnHover(state); + + Selected.Value = true; + return true; + } + + protected override void OnHoverLost(InputState state) + { + base.OnHoverLost(state); + Selected.Value = false; + } + + private void selectionChanged(bool isSelected) + { + if (isSelected) + { + spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); + colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); + glowContainer.FadeIn(glow_fade_duration, Easing.Out); + } + else + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); + spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); + glowContainer.FadeOut(glow_fade_duration, Easing.Out); + } + } + + private void flash() + { + var flash = new Box + { + RelativeSizeAxes = Axes.Both + }; + + colourContainer.Add(flash); + + flash.Colour = ButtonColour; + flash.Blending = BlendingMode.Additive; + flash.Alpha = 0.3f; + flash.FadeOutFromOne(click_duration); + flash.Expire(); + } + + private void updateGlow() + { + leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); + centerGlow.Colour = ButtonColour; + rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); } } } diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index aa9256e576..ea74c67f6c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Caching; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -47,7 +48,16 @@ namespace osu.Game.Graphics.UserInterface set { values = value.ToArray(); - applyPath(); + + float max = values.Max(), min = values.Min(); + if (MaxValue > max) max = MaxValue.Value; + if (MinValue < min) min = MinValue.Value; + + ActualMaxValue = max; + ActualMinValue = min; + + pathCached.Invalidate(); + maskingContainer.Width = 0; maskingContainer.ResizeWidthTo(1, transform_duration, Easing.OutQuint); } @@ -63,13 +73,28 @@ namespace osu.Game.Graphics.UserInterface }); } + private bool pending; + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { - if ((invalidation & Invalidation.DrawSize) != 0) - applyPath(); + if ((invalidation & Invalidation.DrawSize) > 0) + pathCached.Invalidate(); + return base.Invalidate(invalidation, source, shallPropagate); } + private Cached pathCached = new Cached(); + + protected override void Update() + { + base.Update(); + if (!pathCached.IsValid) + { + applyPath(); + pathCached.Validate(); + } + } + private void applyPath() { path.ClearVertices(); @@ -77,13 +102,6 @@ namespace osu.Game.Graphics.UserInterface int count = Math.Max(values.Length, DefaultValueCount); - float max = values.Max(), min = values.Min(); - if (MaxValue > max) max = MaxValue.Value; - if (MinValue < min) min = MinValue.Value; - - ActualMaxValue = max; - ActualMinValue = min; - for (int i = 0; i < values.Length; i++) { float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index e581d19d54..b4ca0d2e02 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using System; +using System.Linq; namespace osu.Game.Graphics.UserInterface { @@ -72,16 +73,9 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), + ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => new Star { Alpha = minStarAlpha }) } }; - - for (int i = 0; i < StarCount; i++) - { - stars.Add(new Star - { - Alpha = minStarAlpha, - }); - } } protected override void LoadComplete() @@ -147,15 +141,12 @@ namespace osu.Game.Graphics.UserInterface { Size = new Vector2(star_size); - Children = new[] + Child = Icon = new SpriteIcon { - Icon = new SpriteIcon - { - Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } + Size = new Vector2(star_size), + Icon = FontAwesome.fa_star, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; } } diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs new file mode 100644 index 0000000000..b218f76b53 --- /dev/null +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.IO.Serialization.Converters +{ + /// + /// A type of that serializes a alongside + /// a lookup table for the types contained. The lookup table is used in deserialization to + /// reconstruct the objects with their original types. + /// + /// The type of objects contained in the this attribute is attached to. + public class TypedListConverter : JsonConverter + { + private readonly bool requiresTypeVersion; + + /// + /// Constructs a new . + /// + // ReSharper disable once UnusedMember.Global + public TypedListConverter() + { + } + + /// + /// Constructs a new . + /// + /// Whether the version of the type should be serialized. + // ReSharper disable once UnusedMember.Global (Used in Beatmap) + public TypedListConverter(bool requiresTypeVersion) + { + this.requiresTypeVersion = requiresTypeVersion; + } + + public override bool CanConvert(Type objectType) => objectType == typeof(List); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var list = new List(); + + var obj = JObject.Load(reader); + var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader()); + + foreach (var tok in obj["items"]) + { + var itemReader = tok.CreateReader(); + + var typeName = lookupTable[(int)tok["type"]]; + var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); + serializer.Populate(itemReader, instance); + + list.Add(instance); + } + + return list; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var list = (List)value; + + var lookupTable = new List(); + var objects = new List(); + foreach (var item in list) + { + var type = item.GetType(); + var assemblyName = type.Assembly.GetName(); + + var typeString = $"{type.FullName}, {assemblyName.Name}"; + if (requiresTypeVersion) + typeString += $", {assemblyName.Version}"; + + int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) + { + lookupTable.Add(typeString); + typeId = lookupTable.Count - 1; + } + + var itemObject = JObject.FromObject(item, serializer); + itemObject.AddFirst(new JProperty("type", typeId)); + objects.Add(itemObject); + } + + writer.WriteStartObject(); + + writer.WritePropertyName("lookup_table"); + serializer.Serialize(writer, lookupTable); + + writer.WritePropertyName("items"); + serializer.Serialize(writer, objects); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs new file mode 100644 index 0000000000..87fbd31d21 --- /dev/null +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OpenTK; + +namespace osu.Game.IO.Serialization.Converters +{ + /// + /// A type of that serializes only the X and Y coordinates of a . + /// + public class Vector2Converter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + return new Vector2((float)obj["x"], (float)obj["y"]); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var vector = (Vector2)value; + + writer.WriteStartObject(); + + writer.WritePropertyName("x"); + writer.WriteValue(vector.X); + writer.WritePropertyName("y"); + writer.WriteValue(vector.Y); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index e725742726..892c266fe3 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; namespace osu.Game.IO.Serialization { @@ -11,20 +12,26 @@ namespace osu.Game.IO.Serialization public static class JsonSerializableExtensions { - public static string Serialize(this IJsonSerializable obj) - { - return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); - } + public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); - public static T Deserialize(this string objString) - { - return JsonConvert.DeserializeObject(objString); - } + public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); - public static T DeepClone(this T obj) - where T : IJsonSerializable + public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); + + public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); + + /// + /// Creates the default that should be used for all s. + /// + /// + public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { - return Deserialize(Serialize(obj)); - } + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + Converters = new JsonConverter[] { new Vector2Converter() }, + ContractResolver = new KeyContractResolver() + }; } } diff --git a/osu.Game/IO/Serialization/KeyContractResolver.cs b/osu.Game/IO/Serialization/KeyContractResolver.cs new file mode 100644 index 0000000000..10375646ce --- /dev/null +++ b/osu.Game/IO/Serialization/KeyContractResolver.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Humanizer; +using Newtonsoft.Json.Serialization; + +namespace osu.Game.IO.Serialization +{ + public class KeyContractResolver : DefaultContractResolver + { + protected override string ResolvePropertyName(string propertyName) + { + return propertyName.Underscore(); + } + } +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index bae14fc1dc..784e6462f2 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Input.Bindings /// A KeyBindingInputManager with a database backing for custom overrides. /// /// The type of the custom action. - public abstract class DatabasedKeyBindingInputManager : KeyBindingInputManager + public class DatabasedKeyBindingInputManager : KeyBindingContainer where T : struct { private readonly RulesetInfo ruleset; @@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. /// Specify how to deal with multiple matches of s and s. - protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) + public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) : base(simultaneousMode) { this.ruleset = ruleset; diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index 759396e195..745508ec9e 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Input.Bindings; namespace osu.Game.Input.Bindings { - public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager + public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager, IHandleGlobalInput { private readonly Drawable handler; diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 95791391f0..9e2988417a 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Input } } - public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings); + public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs new file mode 100644 index 0000000000..6023e88994 --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -0,0 +1,307 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171209034410_AddRulesetInfoShortName")] + partial class AddRulesetInfoShortName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs new file mode 100644 index 0000000000..94519c0b97 --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddRulesetInfoShortName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortName", + table: "RulesetInfo", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo", + column: "ShortName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo"); + + migrationBuilder.DropColumn( + name: "ShortName", + table: "RulesetInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 599d8c8265..937c7b46ef 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -243,10 +243,15 @@ namespace osu.Game.Migrations b.Property("Name"); + b.Property("ShortName"); + b.HasKey("ID"); b.HasIndex("Available"); + b.HasIndex("ShortName") + .IsUnique(); + b.ToTable("RulesetInfo"); }); diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs similarity index 90% rename from osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs rename to osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs index 9e412a9b8b..1b30853421 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs +++ b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs @@ -10,7 +10,7 @@ using System; namespace osu.Game.Online.API.Requests { - public class GetBeatmapSetsResponse : BeatmapMetadata // todo: this is a bit wrong... + public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong... { [JsonProperty(@"covers")] private BeatmapSetOnlineCovers covers { get; set; } @@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests } [JsonProperty(@"beatmaps")] - private IEnumerable beatmaps { get; set; } + private IEnumerable beatmaps { get; set; } public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) { @@ -65,11 +65,11 @@ namespace osu.Game.Online.API.Requests Ranked = ranked, LastUpdated = lastUpdated, }, - Beatmaps = beatmaps.Select(b => b.ToBeatmap(rulesets)).ToList(), + Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), }; } - private class GetBeatmapSetsBeatmapResponse : BeatmapMetadata + private class APIResponseBeatmap : BeatmapMetadata { [JsonProperty(@"id")] private int onlineBeatmapID { get; set; } diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs index e0fdc9adf2..1e6ceaafc6 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -3,7 +3,7 @@ namespace osu.Game.Online.API.Requests { - public class GetBeatmapSetRequest : APIRequest + public class GetBeatmapSetRequest : APIRequest { private readonly int beatmapSetId; diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3777e10a31..065c770738 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -10,19 +10,28 @@ using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests { public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; + private readonly LeaderboardScope scope; + private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (scope == LeaderboardScope.Local) + throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); + this.beatmap = beatmap; + this.scope = scope; + this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); Success += onSuccess; } @@ -33,6 +42,17 @@ namespace osu.Game.Online.API.Requests score.ApplyBeatmap(beatmap); } + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Timeout = 30000; + req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); + req.AddParameter(@"mode", ruleset.ShortName); + + return req; + } + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 923ae0e269..abb473051f 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace osu.Game.Online.API.Requests { - public class GetUserBeatmapsRequest : APIRequest> + public class GetUserBeatmapsRequest : APIRequest> { private readonly long userId; private readonly int offset; @@ -25,7 +25,6 @@ namespace osu.Game.Online.API.Requests public enum BeatmapSetType { - MostPlayed, Favourite, RankedAndApproved, Unranked, diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs new file mode 100644 index 0000000000..431a14085f --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserMostPlayedBeatmapsRequest : APIRequest> + { + private readonly long userId; + private readonly int offset; + + public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0) + { + this.userId = userId; + this.offset = offset; + } + + protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}"; + } + + public class MostPlayedBeatmap + { + [JsonProperty("beatmap_id")] + public int BeatmapID; + + [JsonProperty("count")] + public int PlayCount; + + [JsonProperty] + private BeatmapInfo beatmap; + + [JsonProperty] + private APIResponseBeatmapSet beatmapSet; + + public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) + { + BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); + beatmap.BeatmapSet = setInfo; + beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID; + beatmap.Metadata = setInfo.Metadata; + return beatmap; + } + } +} diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index b949bf35f0..7d40300907 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class SearchBeatmapSetsRequest : APIRequest> + public class SearchBeatmapSetsRequest : APIRequest> { private readonly string query; private readonly RulesetInfo ruleset; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4745733bd9..1dc4c487f3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -16,6 +16,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using OpenTK; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Input.Bindings; using osu.Framework.Platform; @@ -37,7 +38,7 @@ namespace osu.Game private MusicController musicController; - private NotificationOverlay notificationOverlay; + private NotificationOverlay notifications; private DialogOverlay dialogOverlay; @@ -136,7 +137,7 @@ namespace osu.Game if (s.Beatmap == null) { - notificationOverlay.Post(new SimpleNotification + notifications.Post(new SimpleNotification { Text = @"Tried to load a score for a beatmap we don't have!", Icon = FontAwesome.fa_life_saver, @@ -154,7 +155,7 @@ namespace osu.Game base.LoadComplete(); // hook up notifications to components. - BeatmapManager.PostNotification = n => notificationOverlay?.Post(n); + BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; AddRange(new Drawable[] @@ -207,8 +208,9 @@ namespace osu.Game Origin = Anchor.TopRight, }, overlayContent.Add); - loadComponentSingleFile(notificationOverlay = new NotificationOverlay + loadComponentSingleFile(notifications = new NotificationOverlay { + GetToolbarHeight = () => ToolbarOffset, Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -219,15 +221,7 @@ namespace osu.Game Depth = -6, }, overlayContent.Add); - Logger.NewEntry += entry => - { - if (entry.Level < LogLevel.Important) return; - - notificationOverlay.Post(new SimpleNotification - { - Text = $@"{entry.Level}: {entry.Message}" - }); - }; + forwardLoggedErrorsToNotifications(); dependencies.Cache(settings); dependencies.Cache(social); @@ -236,7 +230,7 @@ namespace osu.Game dependencies.Cache(userProfile); dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); - dependencies.Cache(notificationOverlay); + dependencies.Cache(notifications); dependencies.Cache(dialogOverlay); // ensure only one of these overlays are open at once. @@ -271,22 +265,54 @@ namespace osu.Game }; } - settings.StateChanged += delegate + void updateScreenOffset() { - switch (settings.State) - { - case Visibility.Hidden: - intro.MoveToX(0, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); - break; - case Visibility.Visible: - intro.MoveToX(SettingsOverlay.SIDEBAR_WIDTH / 2, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); - break; - } - }; + float offset = 0; + + if (settings.State == Visibility.Visible) + offset += ToolbarButton.WIDTH / 2; + if (notifications.State == Visibility.Visible) + offset -= ToolbarButton.WIDTH / 2; + + screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + } + + settings.StateChanged += _ => updateScreenOffset(); + notifications.StateChanged += _ => updateScreenOffset(); Cursor.State = Visibility.Hidden; } + private void forwardLoggedErrorsToNotifications() + { + int recentErrorCount = 0; + + const double debounce = 5000; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Error || entry.Target == null) return; + + if (recentErrorCount < 2) + { + notifications.Post(new SimpleNotification + { + Icon = FontAwesome.fa_bomb, + Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", + Activated = () => + { + Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + return true; + } + }); + } + + Interlocked.Increment(ref recentErrorCount); + + Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce); + }; + } + private Task asyncLoadStream; private void loadComponentSingleFile(T d, Action add) @@ -351,7 +377,7 @@ namespace osu.Game direct.State = Visibility.Hidden; social.State = Visibility.Hidden; userProfile.State = Visibility.Hidden; - notificationOverlay.State = Visibility.Hidden; + notifications.State = Visibility.Hidden; } private void screenChanged(Screen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8eaa20f781..ea0bf22112 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -154,7 +154,7 @@ namespace osu.Game Debug.Assert(lastBeatmap != null); Debug.Assert(lastBeatmap.Track != null); - lastBeatmap.DisposeTrack(); + lastBeatmap.RecycleTrack(); } Audio.Track.AddItem(b.Track); @@ -177,8 +177,7 @@ namespace osu.Game } catch (MigrationFailedException e) { - Logger.Log((e.InnerException ?? e).ToString(), LoggingTarget.Database, LogLevel.Error); - Logger.Log("Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database, LogLevel.Error); + Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); // if we failed, let's delete the database and start fresh. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. diff --git a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs index 18a0cfd968..47787d2ced 100644 --- a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs @@ -14,10 +14,10 @@ namespace osu.Game.Overlays.BeatmapSet public DownloadButton(string title, string subtitle) { Width = 120; - RelativeSizeAxes = Axes.Y; - Child = new Container + Add(new Container { + Depth = -1, RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Right = 5 }, }, }, - }; + }); } } } diff --git a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs index 9fd4ac177c..1b22853656 100644 --- a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,13 +16,12 @@ namespace osu.Game.Overlays.BeatmapSet { public readonly Bindable Favourited = new Bindable(); - public FavouriteButton() + [BackgroundDependencyLoader] + private void load() { - RelativeSizeAxes = Axes.Y; - Container pink; SpriteIcon icon; - Children = new Drawable[] + AddRange(new Drawable[] { pink = new Container { @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet Size = new Vector2(18), Shadow = false, }, - }; + }); Favourited.ValueChanged += value => { diff --git a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs index 3075020fe6..ac5683de00 100644 --- a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs +++ b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs @@ -2,44 +2,27 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Allocation; namespace osu.Game.Overlays.BeatmapSet { - public class HeaderButton : OsuClickableContainer + public class HeaderButton : TriangleButton { - private readonly Container content; - - protected override Container Content => content; - public HeaderButton() { - CornerRadius = 3; - Masking = true; + Height = 0; + RelativeSizeAxes = Axes.Y; + } - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"094c5f"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"0f7c9b"), - ColourDark = OsuColour.FromHex(@"094c5f"), - TriangleScale = 1.5f, - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }; + [BackgroundDependencyLoader] + private void load() + { + BackgroundColour = OsuColour.FromHex(@"094c5f"); + Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b"); + Triangles.ColourDark = OsuColour.FromHex(@"094c5f"); + Triangles.TriangleScale = 1.5f; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index bd108a193b..b4aea898b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; @@ -176,7 +177,7 @@ namespace osu.Game.Overlays.BeatmapSet Shadow = false, Margin = new MarginPadding { Top = 20 }, }, - textFlow = new TextFlowContainer + textFlow = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 0d658b346e..0a88f586b5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays scores.IsLoading = true; - getScoresRequest = new GetScoresRequest(beatmap); + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); getScoresRequest.Success += r => { scores.Scores = r.Scores; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 9b19b8150e..d2bd50cad6 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog private readonly FillFlowContainer buttonsContainer; private readonly SpriteIcon icon; private readonly SpriteText header; - private readonly SpriteText body; + private readonly TextFlowContainer body; public FontAwesome Icon { @@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Dialog public string BodyText { - get { return body.Text; } set { body.Text = value; } } @@ -220,17 +219,15 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - Text = @"Header", TextSize = 25, Shadow = true, }, - body = new OsuSpriteText + body = new OsuTextFlowContainer(t => t.TextSize = 18) { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Text = @"Body", - TextSize = 18, - Shadow = true, + Padding = new MarginPadding(15), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index a55de951f1..5980cde5fd 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Direct { base.Update(); - if (PreviewPlaying && Preview != null) + if (PreviewPlaying && Preview != null && Preview.IsLoaded) { PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); } diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 9b52cfd367..46ba000a28 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Direct public enum DirectSortCriteria { + Relevance, Title, Artist, Creator, diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 0b7a30797d..7994483043 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -43,6 +43,7 @@ namespace osu.Game.Overlays protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable beatmapSets; + public IEnumerable BeatmapSets { get { return beatmapSets; } @@ -69,6 +70,7 @@ namespace osu.Game.Overlays } private ResultCounts resultAmounts; + public ResultCounts ResultAmounts { get { return resultAmounts; } @@ -115,7 +117,23 @@ namespace osu.Game.Overlays }, }; - Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; }; + Filter.Search.Current.ValueChanged += text => + { + if (text != string.Empty) + { + Header.Tabs.Current.Value = DirectTab.Search; + + if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked) + Filter.Tabs.Current.Value = DirectSortCriteria.Relevance; + } + else + { + Header.Tabs.Current.Value = DirectTab.NewestMaps; + + if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance) + Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; + } + }; ((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch); @@ -271,9 +289,9 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, - ((FilterControl)Filter).Ruleset.Value, - Filter.DisplayStyleControl.Dropdown.Current.Value, - Filter.Tabs.Current.Value); //todo: sort direction (?) + ((FilterControl)Filter).Ruleset.Value, + Filter.DisplayStyleControl.Dropdown.Current.Value, + Filter.Tabs.Current.Value); //todo: sort direction (?) getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 8ebd4ac545..4a7e4f4e6e 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding public override FontAwesome Icon => FontAwesome.fa_osu_hot; public override string Header => "Global"; - public GlobalKeyBindingsSection(KeyBindingInputManager manager) + public GlobalKeyBindingsSection(KeyBindingContainer manager) { Add(new DefaultBindingsSubsection(manager)); } @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.KeyBinding { protected override string Header => string.Empty; - public DefaultBindingsSubsection(KeyBindingInputManager manager) + public DefaultBindingsSubsection(KeyBindingContainer manager) : base(null) { Defaults = manager.DefaultKeyBindings; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 53d77dab6c..c6be428987 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Scale = new Vector2(1f / scale_when_full), }, - description = new TextFlowContainer + description = new OsuTextFlowContainer { TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9ff21dfdd4..9639907914 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -17,22 +17,23 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Mods { public class ModSelectOverlay : WaveOverlayContainer { - private const int button_duration = 700; - private const int ranked_multiplier_duration = 700; private const float content_width = 0.8f; - private Color4 lowMultiplierColour, highMultiplierColour; + protected Color4 LowMultiplierColour, HighMultiplierColour; - private readonly OsuSpriteText rankedLabel; - private readonly OsuSpriteText multiplierLabel; - private readonly FillFlowContainer rankedMultiplerContainer; + protected readonly TriangleButton DeselectAllButton; + protected readonly OsuSpriteText MultiplierLabel; + private readonly FillFlowContainer footerContainer; - private readonly FillFlowContainer modSectionsContainer; + protected override bool BlockPassThroughKeyboard => false; + + protected readonly FillFlowContainer ModSectionsContainer; public readonly Bindable> SelectedMods = new Bindable>(); @@ -42,7 +43,7 @@ namespace osu.Game.Overlays.Mods { var instance = newRuleset.CreateInstance(); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.Mods = instance.GetModsFor(section.ModType); refreshSelectedMods(); } @@ -50,8 +51,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) { - lowMultiplierColour = colours.Red; - highMultiplierColour = colours.Green; + LowMultiplierColour = colours.Red; + HighMultiplierColour = colours.Green; if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -66,14 +67,14 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - rankedMultiplerContainer.MoveToX(rankedMultiplerContainer.DrawSize.X, APPEAR_DURATION, Easing.InSine); - rankedMultiplerContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); } } @@ -81,27 +82,28 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - rankedMultiplerContainer.MoveToX(0, ranked_multiplier_duration, Easing.OutQuint); - rankedMultiplerContainer.FadeIn(ranked_multiplier_duration, Easing.OutQuint); + footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), button_duration, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, button_duration, Easing.OutQuint); - section.ButtonsContainer.FadeIn(button_duration, Easing.OutQuint); + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); } } public void DeselectAll() { - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectAll(); + refreshSelectedMods(); } public void DeselectTypes(Type[] modTypes) { if (modTypes.Length == 0) return; - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectTypes(modTypes); } @@ -114,7 +116,7 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() { - SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); double multiplier = 1.0; bool ranked = true; @@ -129,16 +131,16 @@ namespace osu.Game.Overlays.Mods // 1.05x // 1.20x - multiplierLabel.Text = $"{multiplier:N2}x"; - string rankedString = ranked ? "Ranked" : "Unranked"; - rankedLabel.Text = $@"{rankedString}, Score Multiplier: "; + MultiplierLabel.Text = $"{multiplier:N2}x"; + if (!ranked) + MultiplierLabel.Text += " (Unranked)"; if (multiplier > 1.0) - multiplierLabel.FadeColour(highMultiplierColour, 200); + MultiplierLabel.FadeColour(HighMultiplierColour, 200); else if (multiplier < 1.0) - multiplierLabel.FadeColour(lowMultiplierColour, 200); + MultiplierLabel.FadeColour(LowMultiplierColour, 200); else - multiplierLabel.FadeColour(Color4.White, 200); + MultiplierLabel.FadeColour(Color4.White, 200); } public ModSelectOverlay() @@ -232,7 +234,7 @@ namespace osu.Game.Overlays.Mods }, new OsuSpriteText { - Text = @"Others are just for fun", + Text = @"Others are just for fun.", TextSize = 18, Shadow = true, }, @@ -241,7 +243,7 @@ namespace osu.Game.Overlays.Mods }, }, // Body - modSectionsContainer = new FillFlowContainer + ModSectionsContainer = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -289,7 +291,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - rankedMultiplerContainer = new FillFlowContainer + footerContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -299,26 +301,42 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Horizontal, Padding = new MarginPadding { - Top = 20, - Bottom = 20, + Vertical = 15 }, Children = new Drawable[] { - rankedLabel = new OsuSpriteText + DeselectAllButton = new TriangleButton { - Text = @"Ranked, Score Multiplier: ", + Width = 180, + Text = "Deselect All", + Action = DeselectAll, + Margin = new MarginPadding + { + Right = 20 + } + }, + new OsuSpriteText + { + Text = @"Score Multiplier: ", TextSize = 30, Shadow = true, + Margin = new MarginPadding + { + Top = 5 + } }, - multiplierLabel = new OsuSpriteText + MultiplierLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", - Text = @"1.00x", TextSize = 30, Shadow = true, - }, - }, - }, + Margin = new MarginPadding + { + Top = 5 + } + } + } + } }, }, }, diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index af01cdc451..245b2d36ce 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -35,7 +35,11 @@ namespace osu.Game.Overlays.Music set { base.Padding = value; } } - public IEnumerable BeatmapSets { set { items.Sets = value; } } + public IEnumerable BeatmapSets + { + get { return items.Sets; } + set { items.Sets = value; } + } public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; public BeatmapSetInfo NextSet => items.NextSet; @@ -48,7 +52,7 @@ namespace osu.Game.Overlays.Music } public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet); - public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet); public void Filter(string searchTerm) => items.SearchTerm = searchTerm; @@ -81,6 +85,7 @@ namespace osu.Game.Overlays.Music public IEnumerable Sets { + get { return items.Select(x => x.BeatmapSetInfo).ToList(); } set { items.Clear(); @@ -103,12 +108,11 @@ namespace osu.Game.Overlays.Music }); } - public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID); - if (itemToRemove == null) - return false; - return items.Remove(itemToRemove); + if (itemToRemove != null) + items.Remove(itemToRemove); } public BeatmapSetInfo SelectedSet @@ -230,6 +234,7 @@ namespace osu.Game.Overlays.Music private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { public IEnumerable FilterTerms => new string[] { }; + public bool MatchingFilter { set diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index d05ad85726..d913895159 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music private readonly Bindable beatmapBacking = new Bindable(); - public IEnumerable BeatmapSets; + public IEnumerable BeatmapSets => list.BeatmapSets; [BackgroundDependencyLoader] private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) @@ -74,11 +74,10 @@ namespace osu.Game.Overlays.Music }, }; - beatmaps.BeatmapSetAdded += s => Schedule(() => list.AddBeatmapSet(s)); - beatmaps.BeatmapSetRemoved += s => Schedule(() => list.RemoveBeatmapSet(s)); - - list.BeatmapSets = BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); + beatmaps.BeatmapSetAdded += list.AddBeatmapSet; + beatmaps.BeatmapSetRemoved += list.RemoveBeatmapSet; + list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); beatmapBacking.BindTo(game.Beatmap); @@ -121,7 +120,7 @@ namespace osu.Game.Overlays.Music return; } - playSpecified(set.Beatmaps[0]); + playSpecified(set.Beatmaps.First()); } public void PlayPrevious() @@ -130,7 +129,7 @@ namespace osu.Game.Overlays.Music if (playable != null) { - playSpecified(playable.Beatmaps[0]); + playSpecified(playable.Beatmaps.First()); list.SelectedSet = playable; } } @@ -141,7 +140,7 @@ namespace osu.Game.Overlays.Music if (playable != null) { - playSpecified(playable.Beatmaps[0]); + playSpecified(playable.Beatmaps.First()); list.SelectedSet = playable; } } @@ -149,7 +148,10 @@ namespace osu.Game.Overlays.Music private void playSpecified(BeatmapInfo info) { beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking); - beatmapBacking.Value.Track.Start(); + + var track = beatmapBacking.Value.Track; + + track.Restart(); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d0c7c791e0..d7c553aa7d 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -252,7 +252,7 @@ namespace osu.Game.Overlays playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled) + if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any()) next(); } else diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 260214a14f..2f1c3285ef 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -10,6 +10,8 @@ using osu.Game.Overlays.Notifications; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; +using System; +using osu.Framework.Configuration; namespace osu.Game.Overlays { @@ -19,9 +21,13 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private ScrollContainer scrollContainer; private FlowContainer sections; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + [BackgroundDependencyLoader] private void load() { @@ -36,12 +42,12 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, - Alpha = 0.6f, + Alpha = 0.6f }, - scrollContainer = new OsuScrollContainer + new OsuScrollContainer { + Masking = true, RelativeSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT }, Children = new[] { sections = new FillFlowContainer @@ -55,14 +61,14 @@ namespace osu.Game.Overlays { Title = @"Notifications", ClearText = @"Clear All", - AcceptTypes = new[] { typeof(SimpleNotification) }, + AcceptTypes = new[] { typeof(SimpleNotification) } }, new NotificationSection { Title = @"Running Tasks", ClearText = @"Cancel All", - AcceptTypes = new[] { typeof(ProgressNotification) }, - }, + AcceptTypes = new[] { typeof(ProgressNotification) } + } } } } @@ -70,47 +76,45 @@ namespace osu.Game.Overlays }; } + private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); + private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); + + public readonly BindableInt UnreadCount = new BindableInt(); + private int runningDepth; private void notificationClosed() { // hide ourselves if all notifications have been dismissed. - if (sections.Select(c => c.DisplayedCount).Sum() == 0) + if (totalCount == 0) State = Visibility.Hidden; + + updateCounts(); } - public void Post(Notification notification) + public void Post(Notification notification) => Schedule(() => { - Schedule(() => - { - State = Visibility.Visible; + ++runningDepth; + notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; - ++runningDepth; - notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; + notification.Closed += notificationClosed; - notification.Closed += notificationClosed; + var hasCompletionTarget = notification as IHasCompletionTarget; + if (hasCompletionTarget != null) + hasCompletionTarget.CompletionTarget = Post; - var hasCompletionTarget = notification as IHasCompletionTarget; - if (hasCompletionTarget != null) - hasCompletionTarget.CompletionTarget = Post; + var ourType = notification.GetType(); + sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification); - var ourType = notification.GetType(); - sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification); - }); - } + updateCounts(); + }); protected override void PopIn() { base.PopIn(); - scrollContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH / 2); - } - - private void markAllRead() - { - sections.Children.ForEach(s => s.MarkAllRead()); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); } protected override void PopOut() @@ -120,7 +124,26 @@ namespace osu.Game.Overlays markAllRead(); this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH / 2); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + } + + private void updateCounts() + { + UnreadCount.Value = unreadCount; + } + + private void markAllRead() + { + sections.Children.ForEach(s => s.MarkAllRead()); + + updateCounts(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 422051364e..dc2dcf2d74 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -91,7 +91,6 @@ namespace osu.Game.Overlays.Notifications AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Top = 5, Left = 45, Right = 30 }, @@ -261,4 +260,4 @@ namespace osu.Game.Overlays.Notifications } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 09768ba0ea..2bd0321d12 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Notifications public int DisplayedCount => notifications.Count(n => !n.WasClosed); + public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); + public void Add(Notification notification) => notifications.Add(notification); public IEnumerable AcceptTypes; @@ -157,4 +159,4 @@ namespace osu.Game.Overlays.Notifications notifications?.Children.ForEach(n => n.Read = true); } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 58aff16de0..c39c630858 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using OpenTK; using OpenTK.Graphics; @@ -22,6 +23,8 @@ namespace osu.Game.Overlays.Notifications } } + public string CompletionText { get; set; } = "Task has completed!"; + public float Progress { get { return progressBar.Progress; } @@ -87,12 +90,12 @@ namespace osu.Game.Overlays.Notifications protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification { Activated = CompletionClickAction, - Text = "Task has completed!" + Text = CompletionText }; protected virtual void Completed() { - Expire(); + base.Close(); CompletionTarget?.Invoke(CreateCompletionNotification()); } @@ -112,7 +115,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.Both, }); - Content.Add(textDrawable = new TextFlowContainer(t => + Content.Add(textDrawable = new OsuTextFlowContainer(t => { t.TextSize = 16; }) diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index daf1ac838d..1e691e2a5a 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using OpenTK; namespace osu.Game.Overlays.Notifications @@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Notifications } }); - Content.Add(textDrawable = new TextFlowContainer(t => t.TextSize = 16) + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) { Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, @@ -82,9 +83,11 @@ namespace osu.Game.Overlays.Notifications set { + if (value == base.Read) return; + base.Read = value; - Light.FadeTo(value ? 1 : 0, 100); + Light.FadeTo(value ? 0 : 1, 100); } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index dcab942522..ce0feeb4c6 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays { @@ -63,7 +64,7 @@ namespace osu.Game.Overlays Width = 240, RelativeSizeAxes = Axes.Y, }, - textLine1 = new SpriteText + textLine1 = new OsuSpriteText { Padding = new MarginPadding(10), Font = @"Exo2.0-Black", @@ -72,7 +73,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - textLine2 = new SpriteText + textLine2 = new OsuSpriteText { TextSize = 24, Font = @"Exo2.0-Light", @@ -97,7 +98,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both }, - textLine3 = new SpriteText + textLine3 = new OsuSpriteText { Padding = new MarginPadding { Bottom = 15 }, Font = @"Exo2.0-Bold", diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c7bc5c1d93..18e77cf186 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -27,8 +27,10 @@ namespace osu.Game.Overlays.Profile private readonly OsuTextFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; + private readonly RankGraph rankGraph; - private readonly Container coverContainer, chartContainer, supporterTag; + public readonly SupporterIcon SupporterTag; + private readonly Container coverContainer; private readonly Sprite levelBadge; private readonly SpriteText levelText; private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; @@ -53,6 +55,7 @@ namespace osu.Game.Overlays.Profile { RelativeSizeAxes = Axes.X, Height = cover_height, + Masking = true, Children = new Drawable[] { new Box @@ -92,32 +95,13 @@ namespace osu.Game.Overlays.Profile AutoSizeAxes = Axes.Both, Children = new Drawable[] { - supporterTag = new CircularContainer + SupporterTag = new SupporterIcon { + Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Y = -75, - Size = new Vector2(25, 25), - Masking = true, - BorderThickness = 3, - BorderColour = Color4.White, - Alpha = 0, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - new SpriteIcon - { - Icon = FontAwesome.fa_heart, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(12), - } - } + Size = new Vector2(25, 25) }, new LinkFlowContainer.ProfileLink(user) { @@ -272,7 +256,7 @@ namespace osu.Game.Overlays.Profile } } }, - chartContainer = new Container + new Container { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomCentre, @@ -284,6 +268,10 @@ namespace osu.Game.Overlays.Profile { Colour = Color4.Black.Opacity(0.25f), RelativeSizeAxes = Axes.Both + }, + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both } } } @@ -302,11 +290,7 @@ namespace osu.Game.Overlays.Profile public User User { - get - { - return user; - } - + get { return user; } set { user = value; @@ -324,10 +308,10 @@ namespace osu.Game.Overlays.Profile FillMode = FillMode.Fill, OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, - }, - coverContainer.Add); + }, coverContainer.Add); - if (user.IsSupporter) supporterTag.Show(); + if (user.IsSupporter) + SupporterTag.Show(); if (!string.IsNullOrEmpty(user.Colour)) { @@ -382,7 +366,7 @@ namespace osu.Game.Overlays.Profile } tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Intrerests); + tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); infoTextRight.NewParagraph(); if (!string.IsNullOrEmpty(user.Twitter)) @@ -420,7 +404,7 @@ namespace osu.Game.Overlays.Profile gradeSPlus.DisplayCount = 0; gradeSSPlus.DisplayCount = 0; - chartContainer.Add(new RankChart(user) { RelativeSizeAxes = Axes.Both }); + rankGraph.User.Value = user; } } @@ -472,7 +456,7 @@ namespace osu.Game.Overlays.Profile Width = width, Height = 26 }); - Add(numberText = new SpriteText + Add(numberText = new OsuSpriteText { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -514,7 +498,7 @@ namespace osu.Game.Overlays.Profile { set { - if(value != null) + if (value != null) content.Action = () => Process.Start(value); } } diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankGraph.cs similarity index 63% rename from osu.Game/Overlays/Profile/RankChart.cs rename to osu.Game/Overlays/Profile/RankGraph.cs index 5bd6d30b42..d216789b1a 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -14,30 +14,39 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; +using System.Collections.Generic; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile { - public class RankChart : Container + public class RankGraph : Container { + private const float primary_textsize = 25; + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + private readonly SpriteText rankText, performanceText, relativeText; private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; - private readonly int[] ranks; + private KeyValuePair[] ranks; + public Bindable User = new Bindable(); - private const float primary_textsize = 25, secondary_textsize = 13, padding = 10; - - private readonly User user; - - public RankChart(User user) + public RankGraph() { - this.user = user; - - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; - ranks = userRanks.SkipWhile(x => x == 0).ToArray(); - Padding = new MarginPadding { Vertical = padding }; Children = new Drawable[] { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + TextSize = 14, + Font = @"Exo2.0-RegularItalic", + }, rankText = new OsuSpriteText { Anchor = Anchor.TopCentre, @@ -60,89 +69,103 @@ namespace osu.Game.Overlays.Profile Font = @"Exo2.0-RegularItalic", TextSize = secondary_textsize }, - }; - - if (ranks.Length > 0) - { - Add(graph = new RankChartLineGraph + graph = new RankChartLineGraph { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, + Height = 75, Y = -secondary_textsize, - DefaultValueCount = ranks.Length, - }); + Alpha = 0, + } + }; - graph.OnBallMove += showHistoryRankTexts; - } - } + graph.OnBallMove += showHistoryRankTexts; - private void updateRankTexts() - { - rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank"; - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - relativeText.Text = $"{user.Country?.FullName} #{user.CountryRank:#,0}"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex]:#,0}"; - dayIndex++; - relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago"; - //plural should be handled in a general way + User.ValueChanged += userChanged; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (graph != null) + graph.Colour = colours.Yellow; + } + + private void userChanged(User user) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (user == null) { - graph.Colour = colours.Yellow; - // use logarithmic coordinates - graph.Values = ranks.Select(x => -(float)Math.Log(x)); + rankText.Text = string.Empty; + performanceText.Text = string.Empty; + relativeText.Text = string.Empty; + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); graph.SetStaticBallPosition(); } + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + updateRankTexts(); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + private void updateRankTexts() { - if ((invalidation & Invalidation.DrawSize) != 0) - { - graph.Height = DrawHeight - padding * 2 - primary_textsize - secondary_textsize * 2; - } + rankText.Text = User.Value.Statistics.Rank > 0 ? $"#{User.Value.Statistics.Rank:#,0}" : "no rank"; + performanceText.Text = User.Value.Statistics.PP != null ? $"{User.Value.Statistics.PP:#,0}pp" : string.Empty; + relativeText.Text = $"{User.Value.Country?.FullName} #{User.Value.CountryRank:#,0}"; + } - return base.Invalidate(invalidation, source, shallPropagate); + private void showHistoryRankTexts(int dayIndex) + { + rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; + relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } protected override bool OnHover(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); - graph?.ShowBall(); + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(state.Mouse.Position.X); + graph.ShowBall(); + } return base.OnHover(state); } protected override bool OnMouseMove(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); + if (ranks?.Length > 1) + graph.UpdateBallPosition(state.Mouse.Position.X); + return base.OnMouseMove(state); } protected override void OnHoverLost(InputState state) { - if (graph != null) + if (ranks?.Length > 1) { graph.HideBall(); updateRankTexts(); } + base.OnHoverLost(state); } private class RankChartLineGraph : LineGraph { - private const double fade_duration = 200; - private readonly CircularContainer staticBall; private readonly CircularContainer movingBall; diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs new file mode 100644 index 0000000000..bfd8db5d9c --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +namespace osu.Game.Overlays.Profile.Sections +{ + /// + /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). + /// + public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip + { + private readonly BeatmapInfo beatmap; + + public BeatmapMetadataContainer(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + AutoSizeAxes = Axes.Both; + TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; + } + + public string TooltipText { get; } + + [BackgroundDependencyLoader(true)] + private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) + { + Action = () => + { + if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); + }; + + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = locale.GetUnicodePreference( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] " + ), + TextSize = 15, + Font = "Exo2.0-SemiBoldItalic", + }, + new OsuSpriteText + { + Current = locale.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + TextSize = 12, + Padding = new MarginPadding { Top = 3 }, + Font = "Exo2.0-RegularItalic", + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs new file mode 100644 index 0000000000..0607549f20 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -0,0 +1,124 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class DrawableProfileRow : Container + { + private const int fade_duration = 200; + + private Box underscoreLine; + private readonly Box coloredBackground; + private readonly Container background; + + /// + /// A visual element displayed to the left of content. + /// + protected abstract Drawable CreateLeftVisual(); + + protected FillFlowContainer LeftFlowContainer { get; private set; } + protected FillFlowContainer RightFlowContainer { get; private set; } + + protected override Container Content { get; } + + protected DrawableProfileRow() + { + RelativeSizeAxes = Axes.X; + Height = 60; + InternalChildren = new Drawable[] + { + background = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 3, + Alpha = 0, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 1f, + Colour = Color4.Black.Opacity(0.2f), + }, + Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.97f, + }, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { + AddRange(new Drawable[] + { + underscoreLine = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 1, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + CreateLeftVisual(), + LeftFlowContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10 }, + Direction = FillDirection.Vertical, + }, + } + }, + RightFlowContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Vertical, + }, + }); + + coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; + } + + protected override bool OnClick(InputState state) => true; + + protected override bool OnHover(InputState state) + { + background.FadeIn(fade_duration, Easing.OutQuint); + underscoreLine.FadeOut(fade_duration, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(InputState state) + { + background.FadeOut(fade_duration, Easing.OutQuint); + underscoreLine.FadeIn(fade_duration, Easing.OutQuint); + base.OnHoverLost(state); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs new file mode 100644 index 0000000000..14ab5d8279 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class DrawableMostPlayedRow : DrawableProfileRow + { + private readonly BeatmapInfo beatmap; + private readonly int playCount; + private OsuHoverContainer mapperContainer; + + public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) + { + this.beatmap = beatmap; + this.playCount = playCount; + } + + protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint) + }) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.None, + Size = new Vector2(80, 50), + }; + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profileOverlay) + { + LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); + LeftFlowContainer.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"mapped by ", + TextSize = 12, + }, + mapperContainer = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = beatmap.Metadata.AuthorString, + TextSize = 12, + Font = @"Exo2.0-MediumItalic" + } + } + }, + } + }); + + RightFlowContainer.Add(new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Text = playCount.ToString(), + TextSize = 18, + Font = @"Exo2.0-SemiBoldItalic" + }, + new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Text = @"times played ", + TextSize = 12, + Font = @"Exo2.0-RegularItalic" + }, + } + }); + + if (profileOverlay != null) + mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs new file mode 100644 index 0000000000..e54f012faf --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + { + public PaginatedMostPlayedBeatmapContainer(Bindable user) + :base(user, "Most Played Beatmaps", "No records. :(") + { + ItemsPerPage = 5; + + ItemsContainer.Direction = FillDirection.Vertical; + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); + + req.Success += beatmaps => + { + ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!beatmaps.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (var beatmap in beatmaps) + { + ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index a4d043d20a..ab99abdccd 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -1,7 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Overlays.Profile.Sections.Ranks; namespace osu.Game.Overlays.Profile.Sections @@ -14,7 +16,11 @@ namespace osu.Game.Overlays.Profile.Sections public HistoricalSection() { - Child = new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("); + Children = new Drawable[] + { + new PaginatedMostPlayedBeatmapContainer(User), + new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("), + }; } } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index b59df1214a..aabe3c98de 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -120,7 +121,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu } } }, - new TextFlowContainer(t => { t.TextSize = 19; }) + new OsuTextFlowContainer(t => { t.TextSize = 19; }) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs index e6ba5b26ac..c3296dae4f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class DrawablePerformanceScore : DrawableScore + public class DrawablePerformanceScore : DrawableProfileScore { private readonly double? weight; @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private void load(OsuColour colour) { double pp = Score.PP ?? 0; - Stats.Add(new OsuSpriteText + RightFlowContainer.Add(new OsuSpriteText { Text = $"{pp:0}pp", Anchor = Anchor.TopRight, @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks if (weight.HasValue) { - Stats.Add(new OsuSpriteText + RightFlowContainer.Add(new OsuSpriteText { Text = $"weighted: {pp * weight:0}pp ({weight:P0})", Anchor = Anchor.TopRight, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs new file mode 100644 index 0000000000..06e4684d4c --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public abstract class DrawableProfileScore : DrawableProfileRow + { + private readonly FillFlowContainer metadata; + private readonly ScoreModsContainer modsContainer; + protected readonly Score Score; + + protected DrawableProfileScore(Score score) + { + Score = score; + + RelativeSizeAxes = Axes.X; + Height = 60; + Children = new Drawable[] + { + modsContainer = new ScoreModsContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 60, + Margin = new MarginPadding { Right = 160 } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { + RightFlowContainer.Add(new OsuSpriteText + { + Text = $"accuracy: {Score.Accuracy:P2}", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + Depth = -1, + }); + + LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); + LeftFlowContainer.Add(new OsuSpriteText + { + Text = Score.Date.LocalDateTime.ToShortDateString(), + TextSize = 11, + Colour = OsuColour.Gray(0xAA), + }); + + foreach (Mod mod in Score.Mods) + modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); + } + + protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs deleted file mode 100644 index 35f4778047..0000000000 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Leaderboards; -using osu.Framework.Localisation; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public abstract class DrawableScore : Container - { - private const int fade_duration = 200; - - protected readonly FillFlowContainer Stats; - private readonly FillFlowContainer metadata; - private readonly ScoreModsContainer modsContainer; - protected readonly Score Score; - private readonly Box underscoreLine; - private readonly Box coloredBackground; - private readonly Container background; - - protected DrawableScore(Score score) - { - Score = score; - - RelativeSizeAxes = Axes.X; - Height = 60; - Children = new Drawable[] - { - background = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 3, - Alpha = 0, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 1f, - Colour = Color4.Black.Opacity(0.2f), - }, - Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.97f, - Children = new Drawable[] - { - underscoreLine = new Box - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 1, - }, - new DrawableRank(score.Rank) - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }, - Stats = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Vertical, - }, - metadata = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 70 }, - Direction = FillDirection.Vertical, - Child = new OsuSpriteText - { - Text = score.Date.LocalDateTime.ToShortDateString(), - TextSize = 11, - Colour = OsuColour.Gray(0xAA), - Depth = -1, - }, - }, - modsContainer = new ScoreModsContainer - { - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 60, - Margin = new MarginPadding { Right = 160 } - } - } - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) - { - coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; - - Stats.Add(new OsuSpriteText - { - Text = $"accuracy: {Score.Accuracy:P2}", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Colour = colour.GrayA, - TextSize = 11, - Font = "Exo2.0-RegularItalic", - Depth = -1, - }); - - metadata.Add(new MetadataContainer(Score.Beatmap.Metadata.Title, Score.Beatmap.Metadata.Artist) - { - AutoSizeAxes = Axes.Both, - Action = () => - { - if (Score.Beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(Score.Beatmap.OnlineBeatmapSetID.Value); - }, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Current = locale.GetUnicodePreference( - $"{Score.Beatmap.Metadata.TitleUnicode ?? Score.Beatmap.Metadata.Title} [{Score.Beatmap.Version}] ", - $"{Score.Beatmap.Metadata.Title ?? Score.Beatmap.Metadata.TitleUnicode} [{Score.Beatmap.Version}] " - ), - TextSize = 15, - Font = "Exo2.0-SemiBoldItalic", - }, - new OsuSpriteText - { - Current = locale.GetUnicodePreference(Score.Beatmap.Metadata.ArtistUnicode, Score.Beatmap.Metadata.Artist), - TextSize = 12, - Padding = new MarginPadding { Top = 3 }, - Font = "Exo2.0-RegularItalic", - }, - }, - }, - }); - - foreach (Mod mod in Score.Mods) - modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); - } - - protected override bool OnClick(InputState state) => true; - - protected override bool OnHover(InputState state) - { - background.FadeIn(fade_duration, Easing.OutQuint); - underscoreLine.FadeOut(fade_duration, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(InputState state) - { - background.FadeOut(fade_duration, Easing.OutQuint); - underscoreLine.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); - } - - private class MetadataContainer : OsuHoverContainer, IHasTooltip - { - public string TooltipText { get; set; } - - public MetadataContainer(string title, string artist) - { - TooltipText = $"{artist} - {title}"; - } - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs index 537b208b39..56b2950f89 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class DrawableTotalScore : DrawableScore + public class DrawableTotalScore : DrawableProfileScore { public DrawableTotalScore(Score score) : base(score) @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load() { - Stats.Add(new OsuSpriteText + RightFlowContainer.Add(new OsuSpriteText { Text = Score.TotalScore.ToString("#,###"), Anchor = Anchor.TopRight, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index dc30934990..472800860c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks foreach (OnlineScore score in scores) { - DrawableScore drawableScore; + DrawableProfileScore drawableScore; switch (type) { diff --git a/osu.Game/Overlays/Profile/SupporterIcon.cs b/osu.Game/Overlays/Profile/SupporterIcon.cs new file mode 100644 index 0000000000..570d5a13bb --- /dev/null +++ b/osu.Game/Overlays/Profile/SupporterIcon.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Overlays.Profile +{ + public class SupporterIcon : CircularContainer + { + private readonly Box background; + + public SupporterIcon() + { + Masking = true; + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Masking = true, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + new Triangles + { + TriangleScale = 0.2f, + ColourLight = OsuColour.FromHex(@"ff7db7"), + ColourDark = OsuColour.FromHex(@"de5b95"), + RelativeSizeAxes = Axes.Both, + Velocity = 0.3f, + }, + } + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_heart, + Scale = new Vector2(0.45f), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs new file mode 100644 index 0000000000..7c324658a3 --- /dev/null +++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Settings +{ + /// + /// A with pink colours to mark dangerous/destructive actions. + /// + public class DangerousSettingsButton : SettingsButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + + Triangles.ColourDark = colours.PinkDark; + Triangles.ColourLight = colours.PinkLight; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 9875ee8004..03cd2118b9 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 1f }, - new SettingsEnumDropdown + new SettingsEnumDropdown { - LabelText = "Random beatmap selection", - Bindable = config.GetBindable(OsuSetting.SelectionRandomType), + LabelText = "Random selection algorithm", + Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 5ebac37cc8..392bc6f1bd 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -97,6 +97,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; } } + + public SensitivitySetting() + { + KeyboardStep = 0.01f; + } } private class SensitivitySlider : OsuSliderBar @@ -105,8 +110,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input public SensitivitySlider() { - KeyboardStep = 0.01f; - Current.ValueChanged += newValue => { if (!isDragging && Sensitivity != null) @@ -133,4 +136,4 @@ namespace osu.Game.Overlays.Settings.Sections.Input public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs new file mode 100644 index 0000000000..8ae520651a --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class DeleteAllBeatmapsDialog : PopupDialog + { + public DeleteAllBeatmapsDialog(Action deleteAction) + { + BodyText = "Everything?"; + + Icon = FontAwesome.fa_trash_o; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = deleteAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 4f4f381ae1..9ab4143613 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -15,11 +15,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importButton; private TriangleButton deleteButton; private TriangleButton restoreButton; + private TriangleButton undeleteButton; protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay) { Children = new Drawable[] { @@ -29,17 +30,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importButton.Enabled.Value = false; - Task.Factory.StartNew(beatmaps.ImportFromStable) - .ContinueWith(t => Schedule(() => importButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); + beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); } }, - deleteButton = new SettingsButton + deleteButton = new DangerousSettingsButton { Text = "Delete ALL beatmaps", Action = () => { - deleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => + { + deleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + })); } }, restoreButton = new SettingsButton @@ -55,6 +58,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } }, + undeleteButton = new SettingsButton + { + Text = "Restore all recently deleted beatmaps", + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.UndeleteAll()).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + }, }; } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index d9aac58c54..da50c2b444 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -159,8 +159,6 @@ namespace osu.Game.Overlays.Settings public string TooltipText => "Revert to default"; - public override bool HandleInput => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 798fa00032..a80f6d4da8 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - public const float SIDEBAR_WIDTH = Sidebar.DEFAULT_WIDTH; + private const float sidebar_width = Sidebar.DEFAULT_WIDTH; protected const float WIDTH = 400; @@ -102,7 +102,7 @@ namespace osu.Game.Overlays if (showSidebar) { - AddInternal(Sidebar = new Sidebar { Width = SIDEBAR_WIDTH }); + AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); SectionsContainer.SelectedSection.ValueChanged += section => { @@ -167,7 +167,7 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - Sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = false; diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index ed206e7e1d..b0171feb30 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar SetIcon(FontAwesome.fa_comments); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(ChatOverlay chat) { StateContainer = chat; diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index 7d25440e2c..5c64ae69ec 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar SetIcon(FontAwesome.fa_osu_chevron_down_o); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct) { StateContainer = direct; diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 319dd63bc9..f38c772890 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Toolbar }; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(RulesetStore rulesets, OsuGame game) { foreach (var r in rulesets.AvailableRulesets) @@ -81,7 +81,10 @@ namespace osu.Game.Overlays.Toolbar ruleset.ValueChanged += rulesetChanged; ruleset.DisabledChanged += disabledChanged; - ruleset.BindTo(game.Ruleset); + if (game != null) + ruleset.BindTo(game.Ruleset); + else + ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(); } public override bool HandleInput => !ruleset.Disabled; diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index d150aacdf9..81c57a984f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.fa_music; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(MusicController music) { StateContainer = music; diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index e11a22d675..c093767e52 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -2,8 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Overlays.Toolbar { @@ -11,17 +17,96 @@ namespace osu.Game.Overlays.Toolbar { protected override Anchor TooltipAnchor => Anchor.TopRight; + public BindableInt NotificationCount = new BindableInt(); + + private readonly CountCircle countDisplay; + public ToolbarNotificationButton() { Icon = FontAwesome.fa_bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; + + Add(countDisplay = new CountCircle + { + Alpha = 0, + Height = 16, + RelativePositionAxes = Axes.Both, + Origin = Anchor.Centre, + Position = new Vector2(0.7f, 0.25f), + }); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(NotificationOverlay notificationOverlay) { StateContainer = notificationOverlay; + + if (notificationOverlay != null) + NotificationCount.BindTo(notificationOverlay.UnreadCount); + + NotificationCount.ValueChanged += count => + { + if (count == 0) + countDisplay.FadeOut(200, Easing.OutQuint); + else + { + countDisplay.Count = count; + countDisplay.FadeIn(200, Easing.OutQuint); + } + }; + } + + private class CountCircle : CompositeDrawable + { + private readonly OsuSpriteText countText; + private readonly Circle circle; + + private int count; + + public int Count + { + get { return count; } + set + { + if (count == value) + return; + + if (value > count) + { + circle.FlashColour(Color4.White, 600, Easing.OutQuint); + this.ScaleTo(1.1f).Then().ScaleTo(1, 600, Easing.OutElastic); + } + + count = value; + countText.Text = value.ToString("#,0"); + } + } + + public CountCircle() + { + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -1, + TextSize = 14, + Padding = new MarginPadding(5), + Colour = Color4.White, + UseFullGlyphHeight = true, + Font = "Exo2.0-Bold", + } + }; + } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 69fdd27d5d..59314b8771 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,8 +21,11 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; - Action = stateContainer.ToggleVisibility; - stateContainer.StateChanged += stateChanged; + if (stateContainer != null) + { + Action = stateContainer.ToggleVisibility; + stateContainer.StateChanged += stateChanged; + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index cf4f664e81..d0d76dd5d2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Toolbar TooltipSub = "Change your settings"; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(SettingsOverlay settings) { StateContainer = settings; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 234d6f0f9a..74d1da4384 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.fa_users; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(SocialOverlay chat) { StateContainer = chat; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 7374a9aa44..9aa660147a 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays private ProfileSection[] sections; private GetUserRequest userReq; private APIAccess api; - private ProfileHeader header; + protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; @@ -113,12 +113,12 @@ namespace osu.Game.Overlays Colour = OsuColour.Gray(0.2f) }); - header = new ProfileHeader(user); + Header = new ProfileHeader(user); Add(sectionsContainer = new SectionsContainer { RelativeSizeAxes = Axes.Both, - ExpandableHeader = header, + ExpandableHeader = Header, FixedHeader = tabs, HeaderBackground = new Box { @@ -169,7 +169,7 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - header.User = user; + Header.User = user; foreach (string id in user.ProfileOrder) { diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 50150b6660..3f9703a523 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays private readonly Container contentContainer; + protected override bool BlockPassThroughKeyboard => true; + protected override Container Content => contentContainer; protected Color4 FirstWaveColour diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs new file mode 100644 index 0000000000..36740b96cb --- /dev/null +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class HitObjectComposer : CompositeDrawable + { + private readonly Ruleset ruleset; + + protected ICompositionTool CurrentTool { get; private set; } + + protected HitObjectComposer(Ruleset ruleset) + { + this.ruleset = ruleset; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + RulesetContainer rulesetContainer; + try + { + rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); + } + catch (Exception e) + { + Logger.Error(e, "Could not load beatmap sucessfully!"); + return; + } + + RadioButtonCollection toolboxCollection; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + Name = "Sidebar", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + rulesetContainer, + new SelectionLayer(rulesetContainer.Playfield) + } + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 200), + } + }; + + rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock()); + + toolboxCollection.Items = + new[] { new RadioButton("Select", () => setCompositionTool(null)) } + .Concat( + CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) + ) + .ToList(); + + toolboxCollection.Items[0].Select(); + } + + private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; + + protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); + + protected abstract IReadOnlyList CompositionTools { get; } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs new file mode 100644 index 0000000000..2982a68b3b --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents a marker visible on the border of a which exposes + /// properties that are used to resize a . + /// + public class Handle : CompositeDrawable + { + private const float marker_size = 10; + + /// + /// Invoked when this requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when this wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when this has finished updates to the drag rectangle. + /// + public Action FinishDrag; + + private Color4 normalColour; + private Color4 hoverColour; + + public Handle() + { + Size = new Vector2(marker_size); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = normalColour = colours.Yellow; + hoverColour = colours.YellowDarker; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + var currentRectangle = GetDragRectangle(); + + float left = currentRectangle.Left; + float right = currentRectangle.Right; + float top = currentRectangle.Top; + float bottom = currentRectangle.Bottom; + + // Apply modifications to the capture rectangle + if ((Anchor & Anchor.y0) > 0) + top += state.Mouse.Delta.Y; + else if ((Anchor & Anchor.y2) > 0) + bottom += state.Mouse.Delta.Y; + + if ((Anchor & Anchor.x0) > 0) + left += state.Mouse.Delta.X; + else if ((Anchor & Anchor.x2) > 0) + right += state.Mouse.Delta.X; + + UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + FinishDrag(); + return true; + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(normalColour, 100); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs new file mode 100644 index 0000000000..22d993e7cd --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// A that has s around its border. + /// + public class HandleContainer : CompositeDrawable + { + /// + /// Invoked when a requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when a wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when a has finished updates to the drag rectangle. + /// + public Action FinishDrag; + + public HandleContainer() + { + InternalChildren = new Drawable[] + { + new Handle + { + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.TopRight, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.BottomRight, + Origin = Anchor.Centre + }, + new Handle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre + }, + new OriginHandle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }; + + InternalChildren.OfType().ForEach(m => + { + m.GetDragRectangle = () => GetDragRectangle(); + m.UpdateDragRectangle = r => UpdateDragRectangle(r); + m.FinishDrag = () => FinishDrag(); + }); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs new file mode 100644 index 0000000000..6f73d6b916 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -0,0 +1,178 @@ +// Copyright (c) 2007-2017 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// A box that represents a drag selection. + /// + public class HitObjectSelectionBox : CompositeDrawable + { + public readonly Bindable Selection = new Bindable(); + + /// + /// The s that can be selected through a drag-selection. + /// + public IEnumerable CapturableObjects; + + private readonly Container borderMask; + private readonly Drawable background; + private readonly HandleContainer handles; + + private Color4 captureFinishedColour; + + private readonly Vector2 startPos; + + /// + /// Creates a new . + /// + /// The point at which the drag was initiated, in the parent's coordinates. + public HitObjectSelectionBox(Vector2 startPos) + { + this.startPos = startPos; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-1), + Child = borderMask = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + MaskingSmoothness = 1, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + AlwaysPresent = true + }, + } + }, + handles = new HandleContainer + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + GetDragRectangle = () => dragRectangle, + UpdateDragRectangle = updateDragRectangle, + FinishDrag = FinishCapture + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + captureFinishedColour = colours.Yellow; + } + + /// + /// The secondary corner of the drag selection box. A rectangle will be fit between the starting position and this value. + /// + public Vector2 DragEndPosition { set => updateDragRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, value.X, value.Y)); } + + private RectangleF dragRectangle; + private void updateDragRectangle(RectangleF rectangle) + { + dragRectangle = rectangle; + + Position = new Vector2( + Math.Min(rectangle.Left, rectangle.Right), + Math.Min(rectangle.Top, rectangle.Bottom)); + + Size = new Vector2( + Math.Max(rectangle.Left, rectangle.Right) - Position.X, + Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); + } + + private readonly List capturedHitObjects = new List(); + + /// + /// Processes hitobjects to determine which ones are captured by the drag selection. + /// Captured hitobjects will be enclosed by the drag selection upon . + /// + public void BeginCapture() + { + capturedHitObjects.Clear(); + + foreach (var obj in CapturableObjects) + { + if (!obj.IsAlive || !obj.IsPresent) + continue; + + if (ScreenSpaceDrawQuad.Contains(obj.SelectionPoint)) + capturedHitObjects.Add(obj); + } + } + + /// + /// Encloses hitobjects captured through in the drag selection box. + /// + public void FinishCapture() + { + if (capturedHitObjects.Count == 0) + { + Hide(); + return; + } + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + foreach (var obj in capturedHitObjects) + { + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + this.MoveTo(topLeft, 200, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + + dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); + + borderMask.BorderThickness = 3; + borderMask.FadeColour(captureFinishedColour, 200); + + // Transform into markers to let the user modify the drag selection further. + background.Delay(50).FadeOut(200); + handles.FadeIn(200); + + Selection.Value = new SelectionInfo + { + Objects = capturedHitObjects, + SelectionQuad = Parent.ToScreenSpace(dragRectangle) + }; + } + + private bool isActive = true; + public override bool HandleInput => isActive; + + public override void Hide() + { + isActive = false; + this.FadeOut(400, Easing.OutQuint).Expire(); + + Selection.Value = null; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs new file mode 100644 index 0000000000..8326ebeeac --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents the origin of a . + /// + public class OriginHandle : CompositeDrawable + { + private const float marker_size = 10; + private const float line_width = 2; + + public OriginHandle() + { + Size = new Vector2(marker_size); + + InternalChildren = new[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = line_width + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = line_width + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs new file mode 100644 index 0000000000..aec16bd46d --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class SelectionInfo + { + /// + /// The objects that are captured by the selection. + /// + public IEnumerable Objects; + + /// + /// The screen space quad of the selection box surrounding . + /// + public Quad SelectionQuad; + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs new file mode 100644 index 0000000000..98bcfd0ec8 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class SelectionLayer : CompositeDrawable + { + public readonly Bindable Selection = new Bindable(); + + private readonly Playfield playfield; + + public SelectionLayer(Playfield playfield) + { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + } + + private HitObjectSelectionBox selectionBoxBox; + + protected override bool OnDragStart(InputState state) + { + // Hide the previous drag box - we won't be working with it any longer + selectionBoxBox?.Hide(); + + AddInternal(selectionBoxBox = new HitObjectSelectionBox(ToLocalSpace(state.Mouse.NativeState.Position)) + { + CapturableObjects = playfield.HitObjects.Objects, + }); + + Selection.BindTo(selectionBoxBox.Selection); + + return true; + } + + protected override bool OnDrag(InputState state) + { + selectionBoxBox.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); + selectionBoxBox.BeginCapture(); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + selectionBoxBox.FinishCapture(); + return true; + } + + protected override bool OnClick(InputState state) + { + selectionBoxBox?.Hide(); + return true; + } + } +} diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs new file mode 100644 index 0000000000..70e4d3a0c5 --- /dev/null +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Screens.Play.ReplaySettings; + +namespace osu.Game.Rulesets.Edit +{ + public class ToolboxGroup : ReplayGroup + { + protected override string Title => "toolbox"; + + public ToolboxGroup() + { + RelativeSizeAxes = Axes.X; + Width = 1; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs new file mode 100644 index 0000000000..dd182dcbdb --- /dev/null +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Tools +{ + public class HitObjectCompositionTool : ICompositionTool + where T : HitObject + { + public string Name => typeof(T).Name; + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs new file mode 100644 index 0000000000..eba873f0cf --- /dev/null +++ b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Edit.Tools +{ + public interface ICompositionTool + { + string Name { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 941cedca3f..ab4c04be3f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -14,6 +14,8 @@ using osu.Game.Audio; using System.Linq; using osu.Game.Graphics; using osu.Framework.Configuration; +using OpenTK; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Objects.Drawables { @@ -38,6 +40,16 @@ namespace osu.Game.Rulesets.Objects.Drawables { HitObject = hitObject; } + + /// + /// The screen-space point that causes this to be selected in the Editor. + /// + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + + /// + /// The screen-space quad that outlines this for selections in the Editor. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } public abstract class DrawableHitObject : DrawableHitObject @@ -60,6 +72,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public IReadOnlyList Judgements => judgements; protected List Samples = new List(); + protected virtual IEnumerable GetSamples() => HitObject.Samples; public readonly Bindable State = new Bindable(); @@ -72,15 +85,29 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(AudioManager audio) { - foreach (SampleInfo sample in HitObject.Samples) + var samples = GetSamples(); + if (samples.Any()) { - SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); + if (HitObject.SampleControlPoint == null) + throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - if (channel == null) - continue; + foreach (SampleInfo s in samples) + { + SampleInfo localSampleInfo = new SampleInfo + { + Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, + Name = s.Name, + Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume + }; - channel.Volume.Value = sample.Volume; - Samples.Add(channel); + SampleChannel channel = localSampleInfo.GetChannel(audio.Sample); + + if (channel == null) + continue; + + Samples.Add(channel); + } } } @@ -150,7 +177,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { judgementOccurred = false; - if (AllJudged || State != ArmedState.Idle) + if (AllJudged) return false; if (NestedHitObjects != null) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c69979d4cf..4f06f6afe1 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -1,6 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -21,6 +25,8 @@ namespace osu.Game.Rulesets.Objects /// public virtual double StartTime { get; set; } + private List samples; + /// /// The samples to be played when this hit object is hit. /// @@ -28,41 +34,53 @@ namespace osu.Game.Rulesets.Objects /// and can be treated as the default samples for the hit object. /// /// - public SampleInfoList Samples = new SampleInfoList(); + public List Samples + { + get => samples ?? (samples = new List()); + set => samples = value; + } + + [JsonIgnore] + public SampleControlPoint SampleControlPoint; /// /// Whether this is in Kiai time. /// + [JsonIgnore] public bool Kiai { get; private set; } + private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + + [JsonIgnore] + public IReadOnlyList NestedHitObjects => nestedHitObjects; + /// /// Applies default values to this HitObject. /// /// The control points. /// The difficulty settings to use. - public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime); + ApplyDefaultsToSelf(controlPointInfo, difficulty); + + nestedHitObjects.Clear(); + CreateNestedHitObjects(); + nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty)); + } + + protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - Kiai |= effectPoint.KiaiMode; - - // Initialize first sample - Samples.ForEach(s => initializeSampleInfo(s, soundPoint)); - - // Initialize any repeat samples - var repeatData = this as IHasRepeats; - repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint))); + Kiai = effectPoint.KiaiMode; + SampleControlPoint = samplePoint; } - private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint) + protected virtual void CreateNestedHitObjects() { - if (sample.Volume == 0) - sample.Volume = soundPoint?.SampleVolume ?? 0; - - // If the bank is not assigned a name, assign it from the control point - if (string.IsNullOrEmpty(sample.Bank)) - sample.Bank = soundPoint?.SampleBank ?? @"normal"; } + + protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 667f921e04..fbf02f5345 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d4f9c7191a..bdbd7a9e65 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // Generate the final per-node samples - var nodeSamples = new List(nodes); + var nodeSamples = new List>(nodes); for (int i = 0; i <= repeatCount; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); @@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); - var addbank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]); + var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); + var addbank = (LegacyDecoder.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; @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); /// /// Creates a legacy Spinner-type hit object. @@ -234,9 +234,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The hold end time. protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime); - private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { - var soundTypes = new SampleInfoList + var soundTypes = new List { new SampleInfo { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 479cacc52b..6dc8a07630 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Distance { get; set; } - public List RepeatSamples { get; set; } + public List> RepeatSamples { get; set; } public int RepeatCount { get; set; } = 1; public double EndTime => StartTime + RepeatCount * Distance / Velocity; @@ -46,9 +46,9 @@ namespace osu.Game.Rulesets.Objects.Legacy throw new NotImplementedException(); } - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 86dd40b06e..2060b84222 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 24c205db13..0062d29446 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; +using osu.Game.Audio; namespace osu.Game.Rulesets.Objects.Legacy.Osu { @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 0554cfd97d..529a28ac15 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; +using osu.Game.Audio; namespace osu.Game.Rulesets.Objects.Legacy.Taiko { @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 5abad2d661..2fe2424d49 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). /// - List RepeatSamples { get; } + List> RepeatSamples { get; } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d787da6a0a..64a2157069 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -22,13 +23,12 @@ namespace osu.Game.Rulesets public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() - // Get all mod types as an IEnumerable - .SelectMany(GetModsFor) - // Confine all mods of each mod type into a single IEnumerable - .Where(mod => mod != null) - // Filter out all null mods - .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); - // Resolve MultiMods as their .Mods property + // Confine all mods of each mod type into a single IEnumerable + .SelectMany(GetModsFor) + // Filter out all null mods + .Where(mod => mod != null) + // Resolve MultiMods as their .Mods property + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); public abstract IEnumerable GetModsFor(ModType type); @@ -52,6 +52,8 @@ namespace osu.Game.Rulesets public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null; + public virtual HitObjectComposer CreateHitObjectComposer() => null; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; public abstract string Description { get; } @@ -63,6 +65,11 @@ namespace osu.Game.Rulesets /// public virtual int LegacyID => -1; + /// + /// A unique short name to reference this ruleset in online requests. + /// + public abstract string ShortName { get; } + /// /// A list of available variant ids. /// diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 17f07158df..c5324d92a9 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,18 +3,23 @@ using System; using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int? ID { get; set; } public string Name { get; set; } + public string ShortName { get; set; } + public string InstantiationInfo { get; set; } + [JsonIgnore] public bool Available { get; set; } public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 7aba62e4f1..5cdf46ee46 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -83,7 +83,11 @@ namespace osu.Game.Rulesets { try { - r.CreateInstance(); + var instance = r.CreateInstance(); + + r.Name = instance.Description; + r.ShortName = instance.ShortName; + r.Available = true; } catch @@ -117,6 +121,7 @@ namespace osu.Game.Rulesets private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo { Name = ruleset.Description, + ShortName = ruleset.ShortName, InstantiationInfo = ruleset.GetType().AssemblyQualifiedName, ID = ruleset.LegacyID }; diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index d68642c548..8d09b554dd 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -55,6 +55,12 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } + private readonly Lazy playfield; + /// + /// The playfield. + /// + public Playfield Playfield => playfield.Value; + protected readonly Ruleset Ruleset; /// @@ -64,6 +70,7 @@ namespace osu.Game.Rulesets.UI protected RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; + playfield = new Lazy(CreatePlayfield); } public abstract ScoreProcessor CreateScoreProcessor(); @@ -90,6 +97,12 @@ namespace osu.Game.Rulesets.UI Replay = replay; ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; } + + /// + /// Creates a Playfield. + /// + /// The Playfield. + protected abstract Playfield CreatePlayfield(); } /// @@ -135,11 +148,6 @@ namespace osu.Game.Rulesets.UI public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - /// - /// The playfield. - /// - public Playfield Playfield { get; private set; } - protected override Container Content => content; private Container content; @@ -199,7 +207,7 @@ namespace osu.Game.Rulesets.UI }); AddInternal(KeyBindingInputManager); - KeyBindingInputManager.Add(Playfield = CreatePlayfield()); + KeyBindingInputManager.Add(Playfield); loadObjects(); } @@ -287,12 +295,6 @@ namespace osu.Game.Rulesets.UI /// The HitObject to make drawable. /// The DrawableHitObject. protected abstract DrawableHitObject GetVisualRepresentation(TObject h); - - /// - /// Creates a Playfield. - /// - /// The Playfield. - protected abstract Playfield CreatePlayfield(); } /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 8c4d6de1fe..5cd79cff29 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Timing; @@ -16,11 +18,24 @@ using OpenTK.Input; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : DatabasedKeyBindingInputManager, ICanAttachKeyCounter, IHasReplayHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { - protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) + public class RulesetKeyBindingContainer : DatabasedKeyBindingInputManager { + public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + } + + protected readonly KeyBindingContainer KeyBindingContainer; + + protected override Container Content => KeyBindingContainer; + + protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + { + InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique); } #region Action mapping (for replays) @@ -41,10 +56,10 @@ namespace osu.Game.Rulesets.UI List newActions = replayState.PressedActions; foreach (var released in lastPressedActions.Except(newActions)) - PropagateReleased(KeyBindingInputQueue, released); + KeyBindingContainer.TriggerReleased(released); foreach (var pressed in newActions.Except(lastPressedActions)) - PropagatePressed(KeyBindingInputQueue, pressed); + KeyBindingContainer.TriggerPressed(pressed); lastPressedActions = newActions; } @@ -203,7 +218,7 @@ namespace osu.Game.Rulesets.UI Add(receptor); keyCounter.SetReceptor(receptor); - keyCounter.AddRange(DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); + keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); } public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 405befb80a..3759470c9a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts cpi.TimingPoints.ForEach(addTimingPoint); // Consider all non-timing points as the same type - cpi.SoundPoints.Select(c => (ControlPoint)c) + cpi.SamplePoints.Select(c => (ControlPoint)c) .Concat(cpi.EffectPoints) .Concat(cpi.DifficultyPoints) .Distinct() diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 367cf4337d..b2308aca71 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts if (Beatmap.Value == null) return; + if (Beatmap.Value.Track.Length == double.PositiveInfinity) return; + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 607ff792d8..19d00f3477 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -67,6 +67,8 @@ namespace osu.Game.Screens.Edit { Items = new[] { + new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), + new EditorMenuItemSpacer(), new EditorMenuItem("Exit", MenuItemType.Standard, Exit) } } @@ -136,6 +138,11 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + private void exportBeatmap() + { + Beatmap.Value.Save(); + } + private void onModeChanged(EditorScreenMode mode) { currentScreen?.Exit(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index 2349c261cf..6bc7356f26 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -6,49 +6,99 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Screens.Edit.Screens.Compose { public class Compose : EditorScreen { + private const float vertical_margins = 10; + private const float horizontal_margins = 20; + + private readonly Container composerContainer; + public Compose() { ScrollableTimeline timeline; - Children = new[] + Children = new Drawable[] { - new Container + new GridContainer { - Name = "Timeline", - RelativeSizeAxes = Axes.X, - Height = 110, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - new Box + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, - new Container - { - Name = "Content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 17, Vertical = 10 }, - Children = new Drawable[] + new Container { - new Container + Name = "Timeline", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 115 }, - Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new Container + { + Name = "Timeline content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 115 }, + Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + } + } + } } } + }, + new Drawable[] + { + composerContainer = new Container + { + Name = "Composer content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + } } - } - } + }, + RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } + }, }; timeline.Beatmap.BindTo(Beatmap); + Beatmap.ValueChanged += beatmapChanged; + } + + private void beatmapChanged(WorkingBeatmap newBeatmap) + { + composerContainer.Clear(); + + var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) + { + Logger.Log("Beatmap doesn't have a ruleset assigned."); + // ExitRequested?.Invoke(); + return; + } + + var composer = ruleset.CreateHitObjectComposer(); + if (composer == null) + { + Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); + // ExitRequested?.Invoke(); + return; + } + + composerContainer.Child = composer; } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs new file mode 100644 index 0000000000..10b6c07f3d --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class DrawableRadioButton : TriangleButton + { + /// + /// Invoked when this has been selected. + /// + public Action Selected; + + private Color4 defaultBackgroundColour; + private Color4 defaultBubbleColour; + private Color4 selectedBackgroundColour; + private Color4 selectedBubbleColour; + + private readonly Drawable bubble; + private readonly RadioButton button; + + public DrawableRadioButton(RadioButton button) + { + this.button = button; + + Text = button.Text; + Action = button.Action; + + RelativeSizeAxes = Axes.X; + + bubble = new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.5f), + X = 10, + Masking = true, + Blending = BlendingMode.Additive, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + defaultBackgroundColour = colours.Gray3; + defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); + selectedBackgroundColour = colours.BlueDark; + selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); + + Triangles.Alpha = 0; + + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) + }; + + Add(bubble); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + button.Selected.ValueChanged += v => + { + updateSelectionState(); + if (v) + Selected?.Invoke(button); + }; + + updateSelectionState(); + } + + private void updateSelectionState() + { + if (!IsLoaded) + return; + + BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour; + bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; + } + + protected override bool OnClick(InputState state) + { + if (button.Selected) + return true; + + if (!Enabled) + return true; + + button.Selected.Value = true; + + return base.OnClick(state); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40f + }; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs new file mode 100644 index 0000000000..055362d9e1 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Configuration; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButton + { + /// + /// Whether this is selected. + /// + /// + public readonly BindableBool Selected; + + /// + /// The text that should be displayed in this button. + /// + public string Text; + + /// + /// The that should be invoked when this button is selected. + /// + public Action Action; + + public RadioButton(string text, Action action) + { + Text = text; + Action = action; + Selected = new BindableBool(); + } + + public RadioButton(string text) + : this(text, null) + { + Text = text; + Action = null; + } + + /// + /// Selects this . + /// + public void Select() => Selected.Value = true; + + /// + /// Deselects this . + /// + public void Deselect() => Selected.Value = false; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs new file mode 100644 index 0000000000..5f1def4a2e --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButtonCollection : CompositeDrawable + { + private IReadOnlyList items; + public IReadOnlyList Items + { + get { return items; } + set + { + if (ReferenceEquals(items, value)) + return; + items = value; + + buttonContainer.Clear(); + items.ForEach(addButton); + } + } + + private readonly FlowContainer buttonContainer; + + public RadioButtonCollection() + { + AutoSizeAxes = Axes.Y; + + InternalChild = buttonContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + } + + private RadioButton currentlySelected; + private void addButton(RadioButton button) + { + button.Selected.ValueChanged += v => + { + if (v) + { + currentlySelected?.Deselect(); + currentlySelected = button; + } + else + currentlySelected = null; + }; + + buttonContainer.Add(new DrawableRadioButton(button)); + } + } +} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index ca541ea552..ec2e8e0cb1 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -1,8 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shaders; using osu.Game.Screens.Menu; using OpenTK; using osu.Framework.Screens; @@ -33,14 +37,28 @@ namespace osu.Game.Screens logo.FadeInFromZero(5000, Easing.OutQuint); } + private OsuScreen loadScreen; + private ShaderPrecompiler precompiler; + protected override void OnEntering(Screen last) { base.OnEntering(last); - if (showDisclaimer) - LoadComponentAsync(new Disclaimer(), d => Push(d)); - else - LoadComponentAsync(new Intro(), d => Push(d)); + LoadComponentAsync(precompiler = new ShaderPrecompiler(loadIfReady), Add); + LoadComponentAsync(loadScreen = showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(), s => loadIfReady()); + } + + private void loadIfReady() + { + if (ChildScreen == loadScreen) return; + + if (loadScreen.LoadState != LoadState.Ready) + return; + + if (!precompiler.FinishedCompiling) + return; + + Push(loadScreen); } protected override void LogoSuspending(OsuLogo logo) @@ -54,5 +72,49 @@ namespace osu.Game.Screens { showDisclaimer = game.IsDeployedBuild; } + + /// + /// Compiles a set of shaders before continuing. Attempts to draw some frames between compilation by limiting to one compile per draw frame. + /// + public class ShaderPrecompiler : Drawable + { + private readonly Action onLoaded; + private readonly List loadTargets = new List(); + + public bool FinishedCompiling { get; private set; } + + public ShaderPrecompiler(Action onLoaded) + { + this.onLoaded = onLoaded; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager manager) + { + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); + + loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); + + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); + } + + private Shader currentLoadTarget; + + protected override void Update() + { + base.Update(); + + // if our target is null we are done. + if (loadTargets.All(s => s.Loaded)) + { + FinishedCompiling = true; + Expire(); + onLoaded?.Invoke(); + } + } + } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 76ee4a607e..4a27c7f1ea 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -13,6 +13,8 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Screens.Menu; +using osu.Framework.Input; +using OpenTK.Input; namespace osu.Game.Screens { @@ -73,6 +75,20 @@ namespace osu.Game.Screens sampleExit = audio.Sample.Get(@"UI/screen-back"); } + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat || !IsCurrentScreen) return false; + + switch (args.Key) + { + case Key.Escape: + Exit(); + return true; + } + + return base.OnKeyDown(state, args); + } + protected override void OnResuming(Screen last) { base.OnResuming(last); diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 3e31da2348..09f2e15c57 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -10,7 +10,7 @@ using System.Linq; namespace osu.Game.Screens.Play { - public class FailOverlay : MenuOverlay + public class FailOverlay : GameplayMenuOverlay { public override string Header => "failed"; public override string Description => "you're dead, try again?"; @@ -18,15 +18,15 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton("Retry", colours.YellowDark, OnRetry); - AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (!args.Repeat && args.Key == Key.Escape) { - Buttons.Children.Last().TriggerOnClick(); + InternalButtons.Children.Last().TriggerOnClick(); return true; } diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs similarity index 56% rename from osu.Game/Screens/Play/MenuOverlay.cs rename to osu.Game/Screens/Play/GameplayMenuOverlay.cs index 0a8e172e57..6969cd915b 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -13,10 +13,12 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; +using OpenTK.Input; +using System.Collections.Generic; namespace osu.Game.Screens.Play { - public abstract class MenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition + public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition { private const int transition_duration = 200; private const int button_height = 70; @@ -24,81 +26,24 @@ namespace osu.Game.Screens.Play protected override bool BlockPassThroughKeyboard => true; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public Action OnRetry; public Action OnQuit; public abstract string Header { get; } public abstract string Description { get; } - protected FillFlowContainer Buttons; - - public int Retries - { - set - { - if (retryCounterContainer != null) - { - // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" - - retryCounterContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $"{value:n0}", - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $" time{(value == 1 ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - } - }; - } - } - } + protected internal FillFlowContainer InternalButtons; + public IReadOnlyList Buttons => InternalButtons; private FillFlowContainer retryCounterContainer; - public override bool HandleInput => State == Visibility.Visible; - - protected override void PopIn() => this.FadeIn(transition_duration, Easing.In); - protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); - - // Don't let mouse down events through the overlay or people can click circles while paused. - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; - - protected override bool OnMouseMove(InputState state) => true; - - protected void AddButton(string text, Color4 colour, Action action) + protected GameplayMenuOverlay() { - Buttons.Add(new Button - { - Text = text, - ButtonColour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Height = button_height, - Action = delegate - { - action?.Invoke(); - Hide(); - } - }); + RelativeSizeAxes = Axes.Both; + + StateChanged += s => selectionIndex = -1; } [BackgroundDependencyLoader] @@ -154,7 +99,7 @@ namespace osu.Game.Screens.Play } } }, - Buttons = new FillFlowContainer + InternalButtons = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -179,16 +124,153 @@ namespace osu.Game.Screens.Play }, }; - Retries = 0; + updateRetryCount(); } - protected MenuOverlay() + private int retries; + + public int Retries { - RelativeSizeAxes = Axes.Both; + set + { + if (value == retries) + return; + + retries = value; + if (retryCounterContainer != null) + updateRetryCount(); + } } - public class Button : DialogButton + public override bool HandleInput => State == Visibility.Visible; + + protected override void PopIn() => this.FadeIn(transition_duration, Easing.In); + protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); + + // Don't let mouse down events through the overlay or people can click circles while paused. + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + + protected override bool OnMouseMove(InputState state) => true; + + protected void AddButton(string text, Color4 colour, Action action) { + var button = new Button + { + Text = text, + ButtonColour = colour, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Height = button_height, + Action = delegate + { + action?.Invoke(); + Hide(); + } + }; + + button.Selected.ValueChanged += s => buttonSelectionChanged(button, s); + + InternalButtons.Add(button); + } + + private int _selectionIndex = -1; + + private int selectionIndex + { + get { return _selectionIndex; } + set + { + if (_selectionIndex == value) + return; + + // Deselect the previously-selected button + if (_selectionIndex != -1) + InternalButtons[_selectionIndex].Selected.Value = false; + + _selectionIndex = value; + + // Select the newly-selected button + if (_selectionIndex != -1) + InternalButtons[_selectionIndex].Selected.Value = true; + } + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!args.Repeat) + { + switch (args.Key) + { + case Key.Up: + if (selectionIndex == -1 || selectionIndex == 0) + selectionIndex = InternalButtons.Count - 1; + else + selectionIndex--; + return true; + case Key.Down: + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) + selectionIndex = 0; + else + selectionIndex++; + return true; + } + } + + return base.OnKeyDown(state, args); + } + + private void buttonSelectionChanged(DialogButton button, bool isSelected) + { + if (!isSelected) + selectionIndex = -1; + else + selectionIndex = InternalButtons.IndexOf(button); + } + + private void updateRetryCount() + { + // "You've retried 1,065 times in this session" + // "You've retried 1 time in this session" + + retryCounterContainer.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $"{retries:n0}", + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $" time{(retries == 1 ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + } + }; + } + + private class Button : DialogButton + { + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat || args.Key != Key.Enter || !Selected) + return false; + + OnClick(state); + return true; + } } } } diff --git a/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs index e44a738d55..c07eebdef8 100644 --- a/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs @@ -16,8 +16,6 @@ namespace osu.Game.Screens.Play.HUD public bool ReplayLoaded; - public override bool HandleInput => true; - public readonly PlaybackSettings PlaybackSettings; //public readonly CollectionSettings CollectionSettings; //public readonly DiscussionSettings DiscussionSettings; @@ -35,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), Margin = new MarginPadding { Top = 100, Right = 10 }, - Children = new [] + Children = new[] { //CollectionSettings = new CollectionSettings(), //DiscussionSettings = new DiscussionSettings(), diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index d21be71785..2f077f5c0a 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -141,8 +141,6 @@ namespace osu.Game.Screens.Play public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - public override bool HandleInput => true; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyDown(state, args)); protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyUp(state, args)); diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 5f5eeb63a0..3bd28511c7 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play base.Update(); } - public class PauseOverlay : MenuOverlay + public class PauseOverlay : GameplayMenuOverlay { public Action OnResume; @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Play { if (!args.Repeat && args.Key == Key.Escape) { - Buttons.Children.First().TriggerOnClick(); + InternalButtons.Children.First().TriggerOnClick(); return true; } @@ -140,9 +140,9 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton("Continue", colours.Green, OnResume); - AddButton("Retry", colours.YellowDark, OnRetry); - AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc746b305c..2cbb203c37 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Backgrounds; using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; using osu.Framework.Threading; using osu.Game.Rulesets.Mods; @@ -126,7 +125,7 @@ namespace osu.Game.Screens.Play } catch (Exception e) { - Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error); + Logger.Error(e, "Could not load beatmap sucessfully!"); //couldn't load, hard abort! Exit(); @@ -161,8 +160,8 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - Retries = RestartCount, OnPause = () => { + pauseContainer.Retries = RestartCount; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; }, OnResume = () => { @@ -245,7 +244,7 @@ namespace osu.Game.Screens.Play private void initializeStoryboard(bool asyncLoad) { - var beatmap = Beatmap.Value.Beatmap; + var beatmap = Beatmap.Value; storyboard = beatmap.Storyboard.CreateDrawable(Beatmap.Value); storyboard.Masking = true; @@ -327,11 +326,6 @@ namespace osu.Game.Screens.Play { adjustableSourceClock.Reset(); - // this is temporary until we have blocking (async.Wait()) audio component methods. - // then we can call ResetAsync().Wait() or the blocking version above. - while (adjustableSourceClock.IsRunning) - Thread.Sleep(1); - Schedule(() => { decoupledClock.ChangeSource(adjustableSourceClock); @@ -388,7 +382,7 @@ namespace osu.Game.Screens.Play initializeStoryboard(true); var beatmap = Beatmap.Value; - var storyboardVisible = showStoryboard && beatmap.Beatmap.Storyboard.HasDrawable; + var storyboardVisible = showStoryboard && beatmap.Storyboard.HasDrawable; storyboardContainer.FadeColour(new Color4(opacity, opacity, opacity, 1), 800); storyboardContainer.FadeTo(storyboardVisible && opacity > 0 ? 1 : 0); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index de67bef004..a220dcee0e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, - new MetadataLine("Mapper", metadata.Author.Username) + new MetadataLine("Mapper", metadata.AuthorString) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 8e27cb235c..406887624c 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -17,6 +17,7 @@ using OpenTK.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Ranking { @@ -183,7 +184,7 @@ namespace osu.Game.Screens.Ranking Height = 50, Margin = new MarginPadding { Bottom = 110 }, }, - new SpriteText + new OsuSpriteText { Text = $"{score.MaxCombo}x", TextSize = 40, @@ -194,7 +195,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, }, - new SpriteText + new OsuSpriteText { Text = "max combo", TextSize = 20, @@ -204,7 +205,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.TopCentre, }, - new SpriteText + new OsuSpriteText { Text = $"{score.Accuracy:P2}", TextSize = 40, @@ -215,7 +216,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, }, - new SpriteText + new OsuSpriteText { Text = "accuracy", TextSize = 20, diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 911b688669..25a42cae1c 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -201,14 +201,14 @@ namespace osu.Game.Screens.Ranking { Children = new Drawable[] { - new SpriteText { + new OsuSpriteText { Text = statistic.Value.ToString().PadLeft(4, '0'), Colour = colours.Gray7, TextSize = 30, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - new SpriteText { + new OsuSpriteText { Text = statistic.Key, Colour = colours.Gray7, Font = @"Exo2.0-Bold", @@ -324,7 +324,14 @@ namespace osu.Game.Screens.Ranking title.Colour = artist.Colour = colours.BlueDarker; versionMapper.Colour = colours.Gray8; - versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.Metadata.Author.Username}"; + var creator = beatmap.Metadata.Author?.Username; + if (!string.IsNullOrEmpty(creator)) { + versionMapper.Text = $"mapped by {creator}"; + + if (!string.IsNullOrEmpty(beatmap.Version)) + versionMapper.Text = $"{beatmap.Version} - " + versionMapper.Text; + } + title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b0a636dfb3..9d7867659d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; -using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Framework.Input; using OpenTK.Input; @@ -15,490 +14,308 @@ using osu.Framework.MathUtils; using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Screens.Select.Carousel; namespace osu.Game.Screens.Select { public class BeatmapCarousel : OsuScrollContainer { - public BeatmapInfo SelectedBeatmap => selectedPanel?.Beatmap; + /// + /// Triggered when the loaded change and are completely loaded. + /// + public Action BeatmapSetsChanged; + + /// + /// The currently selected beatmap. + /// + public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; + + private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State == CarouselItemState.Selected); + + /// + /// The currently selected beatmap set. + /// + public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; + + private CarouselBeatmapSet selectedBeatmapSet; + + /// + /// Raised when the is changed. + /// + public Action SelectionChanged; public override bool HandleInput => AllowSelection; - public Action BeatmapsChanged; + private IEnumerable beatmapSets => root.Children.OfType(); - public IEnumerable Beatmaps + public IEnumerable BeatmapSets { - get { return groups.Select(g => g.BeatmapSet); } - + get { return beatmapSets.Select(g => g.BeatmapSet); } set { - scrollableContent.Clear(false); - panels.Clear(); - groups.Clear(); - - List newGroups = null; + CarouselGroup newRoot = new CarouselGroupEagerSelect(); Task.Run(() => { - newGroups = value.Select(createGroup).ToList(); - criteria.Filter(newGroups); - }).ContinueWith(t => - { - Schedule(() => - { - foreach (var g in newGroups) - if (g != null) addGroup(g); + value.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); + newRoot.Filter(activeCriteria); - computeYPositions(); - BeatmapsChanged?.Invoke(); - }); - }); + // preload drawables as the ctor overhead is quite high currently. + var _ = newRoot.Drawables; + }).ContinueWith(_ => Schedule(() => + { + root = newRoot; + scrollableContent.Clear(false); + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); + + Schedule(() => BeatmapSetsChanged?.Invoke()); + })); } } private readonly List yPositions = new List(); + private Cached itemsCache = new Cached(); + private Cached scrollPositionCache = new Cached(); - /// - /// Required for now unfortunately. - /// - private BeatmapManager manager; + private readonly Container scrollableContent; - private readonly Container scrollableContent; + public Bindable RandomAlgorithm = new Bindable(); + private readonly List previouslyVisitedRandomSets = new List(); + private readonly Stack randomSelectedBeatmaps = new Stack(); - private readonly List groups = new List(); - - private Bindable randomType; - private readonly List seenGroups = new List(); - - private readonly List panels = new List(); - - private readonly Stack> randomSelectedBeatmaps = new Stack>(); - - private BeatmapGroup selectedGroup; - private BeatmapPanel selectedPanel; + protected List Items = new List(); + private CarouselGroup root = new CarouselGroupEagerSelect(); public BeatmapCarousel() { - Add(new OsuContextMenuContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = scrollableContent = new Container + Child = scrollableContent = new Container { RelativeSizeAxes = Axes.X, } - }); + }; } - public void AddBeatmap(BeatmapSetInfo beatmapSet) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); + } + + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { Schedule(() => { - var group = createGroup(beatmapSet); + var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - if (group == null) + if (existingSet == null) return; - addGroup(group); - computeYPositions(); - if (selectedGroup == null) - selectGroup(group); + root.RemoveChild(existingSet); + itemsCache.Invalidate(); }); } - public void RemoveBeatmap(BeatmapSetInfo beatmapSet) + public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - Schedule(() => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); - } - - public void UpdateBeatmap(BeatmapInfo beatmap) - { - // todo: this method should not run more than once for the same BeatmapSetInfo. - var set = manager.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); - - // todo: this method should be smarter as to not recreate panels that haven't changed, etc. - var group = groups.Find(b => b.BeatmapSet.ID == set.ID); - - if (group == null) - return; - - int i = groups.IndexOf(group); - groups.RemoveAt(i); - - var newGroup = createGroup(set); - - if (newGroup != null) - groups.Insert(i, newGroup); - - bool hadSelection = selectedGroup == group; - - if (hadSelection && newGroup == null) - selectedGroup = null; - - Filter(null, false); - - //check if we can/need to maintain our current selection. - if (hadSelection && newGroup != null) + Schedule(() => { - var newSelection = - newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ?? - newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, group.BeatmapPanels.IndexOf(selectedPanel))]; + CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - selectGroup(newGroup, newSelection); - } - } + bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; - public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) - { - if (beatmap == null || beatmap.Hidden) - { - SelectNext(); - return; - } + var newSet = createCarouselSet(beatmapSet); - if (beatmap == SelectedBeatmap) return; + if (existingSet != null) + root.RemoveChild(existingSet); - foreach (BeatmapGroup group in groups) - { - var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); - if (panel != null) + if (newSet == null) { - selectGroup(group, panel, animated); + itemsCache.Invalidate(); + return; + } + + root.AddChild(newSet); + + applyActiveCriteria(false, false); + + //check if we can/need to maintain our current selection. + if (hadSelection) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); + + itemsCache.Invalidate(); + Schedule(() => BeatmapSetsChanged?.Invoke()); + }); + } + + public void SelectBeatmap(BeatmapInfo beatmap) + { + if (beatmap?.Hidden != false) + return; + + foreach (CarouselBeatmapSet group in beatmapSets) + { + var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + if (item != null) + { + select(item); return; } } } - public Action SelectionChanged; - - public Action StartRequested; - - public Action DeleteRequested; - - public Action RestoreRequested; - - public Action EditRequested; - - public Action HideDifficultyRequested; - - private void selectNullBeatmap() - { - selectedGroup = null; - selectedPanel = null; - SelectionChanged?.Invoke(null); - } - + /// + /// Increment selection in the carousel in a chosen direction. + /// + /// The direction to increment. Negative is backwards. + /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (groups.All(g => g.State == BeatmapGroupState.Hidden)) - { - selectNullBeatmap(); + if (!Items.Any()) return; - } - if (!skipDifficulties && selectedGroup != null) + int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.First()); + int currentIndex = originalIndex; + + // local function to increment the index in the required direction, wrapping over extremities. + int incrementIndex() => currentIndex = (currentIndex + direction + Items.Count) % Items.Count; + + while (incrementIndex() != originalIndex) { - int i = selectedGroup.BeatmapPanels.IndexOf(selectedPanel) + direction; + var item = Items[currentIndex].Item; - if (i >= 0 && i < selectedGroup.BeatmapPanels.Count) + if (item.Filtered || item.State == CarouselItemState.Selected) continue; + + switch (item) { - //changing difficulty panel, not set. - selectGroup(selectedGroup, selectedGroup.BeatmapPanels[i]); - return; + case CarouselBeatmap beatmap: + if (skipDifficulties) continue; + select(beatmap); + return; + case CarouselBeatmapSet set: + if (skipDifficulties) + select(set); + else + select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered) : set.Beatmaps.Last(b => !b.Filtered)); + return; } } - - int startIndex = Math.Max(0, groups.IndexOf(selectedGroup)); - int index = startIndex; - - do - { - index = (index + direction + groups.Count) % groups.Count; - if (groups[index].State != BeatmapGroupState.Hidden) - { - if (skipDifficulties) - SelectBeatmap(groups[index].SelectedPanel != null ? groups[index].SelectedPanel.Beatmap : groups[index].BeatmapPanels.First().Beatmap); - else - SelectBeatmap(direction == 1 ? groups[index].BeatmapPanels.First().Beatmap : groups[index].BeatmapPanels.Last().Beatmap); - - return; - } - } while (index != startIndex); } - private IEnumerable getVisibleGroups() => groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden); - public void SelectNextRandom() { - if (groups.Count == 0) + var visible = beatmapSets.Where(s => !s.Filtered).ToList(); + if (!visible.Any()) return; - var visibleGroups = getVisibleGroups(); - if (!visibleGroups.Any()) - return; - - if (selectedGroup != null) - randomSelectedBeatmaps.Push(new KeyValuePair(selectedGroup, selectedGroup.SelectedPanel)); - - BeatmapGroup group; - - if (randomType == SelectionRandomType.RandomPermutation) + if (selectedBeatmap != null) { - var notSeenGroups = visibleGroups.Except(seenGroups); - if (!notSeenGroups.Any()) + randomSelectedBeatmaps.Push(selectedBeatmap); + + // when performing a random, we want to add the current set to the previously visited list + // else the user may be "randomised" to the existing selection. + if (previouslyVisitedRandomSets.LastOrDefault() != selectedBeatmapSet) + previouslyVisitedRandomSets.Add(selectedBeatmapSet); + } + + CarouselBeatmapSet set; + + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + { + var notYetVisitedSets = visible.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { - seenGroups.Clear(); - notSeenGroups = visibleGroups; + previouslyVisitedRandomSets.Clear(); + notYetVisitedSets = visible; } - group = notSeenGroups.ElementAt(RNG.Next(notSeenGroups.Count())); - seenGroups.Add(group); + set = notYetVisitedSets.ElementAt(RNG.Next(notYetVisitedSets.Count)); + previouslyVisitedRandomSets.Add(set); } else - group = visibleGroups.ElementAt(RNG.Next(visibleGroups.Count())); + set = visible.ElementAt(RNG.Next(visible.Count)); - BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)]; - - selectGroup(group, panel); + select(set.Beatmaps.Skip(RNG.Next(set.Beatmaps.Count())).FirstOrDefault()); } public void SelectPreviousRandom() { - if (!randomSelectedBeatmaps.Any()) - return; - - var visibleGroups = getVisibleGroups(); - if (!visibleGroups.Any()) - return; - while (randomSelectedBeatmaps.Any()) { - var beatmapCoordinates = randomSelectedBeatmaps.Pop(); - var group = beatmapCoordinates.Key; - if (visibleGroups.Contains(group)) + var beatmap = randomSelectedBeatmaps.Pop(); + + if (!beatmap.Filtered) { - selectGroup(group, beatmapCoordinates.Value); + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + previouslyVisitedRandomSets.Remove(selectedBeatmapSet); + select(beatmap); break; } } } - private FilterCriteria criteria = new FilterCriteria(); + private void select(CarouselItem item) + { + if (item == null) return; + item.State.Value = CarouselItemState.Selected; + } - private ScheduledDelegate filterTask; + private FilterCriteria activeCriteria = new FilterCriteria(); + + protected ScheduledDelegate FilterTask; public bool AllowSelection = true; - public void FlushPendingFilters() + public void FlushPendingFilterOperations() { - if (filterTask?.Completed == false) - Filter(null, false); + if (FilterTask?.Completed == false) + applyActiveCriteria(false, false); } - public void Filter(FilterCriteria newCriteria = null, bool debounce = true) + public void Filter(FilterCriteria newCriteria, bool debounce = true) { if (newCriteria != null) - criteria = newCriteria; + activeCriteria = newCriteria; - if (!IsLoaded) return; + applyActiveCriteria(debounce, true); + } - Action perform = delegate + private void applyActiveCriteria(bool debounce, bool scroll) + { + if (root.Children.Any() != true) return; + + void perform() { - filterTask = null; + FilterTask = null; - criteria.Filter(groups); + root.Filter(activeCriteria); + itemsCache.Invalidate(); + if (scroll) scrollPositionCache.Invalidate(); + } - var filtered = new List(groups); - - scrollableContent.Clear(false); - panels.Clear(); - groups.Clear(); - - foreach (var g in filtered) - addGroup(g); - - computeYPositions(); - - if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden) - SelectNext(); - else - selectGroup(selectedGroup, selectedPanel); - }; - - filterTask?.Cancel(); - filterTask = null; + FilterTask?.Cancel(); + FilterTask = null; if (debounce) - filterTask = Scheduler.AddDelayed(perform, 250); + FilterTask = Scheduler.AddDelayed(perform, 250); else perform(); } - public void ScrollToSelected(bool animated = true) - { - float selectedY = computeYPositions(animated); - ScrollTo(selectedY, animated); - } + private float? scrollTarget; - private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Beatmaps.All(b => b.Hidden)) - return null; - - foreach (var b in beatmapSet.Beatmaps) - { - if (b.Metadata == null) - b.Metadata = beatmapSet.Metadata; - } - - return new BeatmapGroup(beatmapSet, manager) - { - SelectionChanged = (g, p) => selectGroup(g, p), - StartRequested = b => StartRequested?.Invoke(), - DeleteRequested = b => DeleteRequested?.Invoke(b), - RestoreHiddenRequested = s => RestoreRequested?.Invoke(s), - EditRequested = b => EditRequested?.Invoke(b), - HideDifficultyRequested = b => HideDifficultyRequested?.Invoke(b), - State = BeatmapGroupState.Collapsed - }; - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager manager, OsuConfigManager config) - { - this.manager = manager; - - randomType = config.GetBindable(OsuSetting.SelectionRandomType); - } - - private void addGroup(BeatmapGroup group) - { - groups.Add(group); - panels.Add(group.Header); - panels.AddRange(group.BeatmapPanels); - } - - private void removeGroup(BeatmapGroup group) - { - if (group == null) - return; - - if (selectedGroup == group) - { - if (getVisibleGroups().Count() == 1) - selectNullBeatmap(); - else - SelectNext(); - } - - groups.Remove(group); - panels.Remove(group.Header); - foreach (var p in group.BeatmapPanels) - panels.Remove(p); - - scrollableContent.Remove(group.Header); - scrollableContent.RemoveRange(group.BeatmapPanels); - - computeYPositions(); - } - - /// - /// Computes the target Y positions for every panel in the carousel. - /// - /// The Y position of the currently selected panel. - private float computeYPositions(bool animated = true) - { - yPositions.Clear(); - - float currentY = DrawHeight / 2; - float selectedY = currentY; - - foreach (BeatmapGroup group in groups) - { - movePanel(group.Header, group.State != BeatmapGroupState.Hidden, animated, ref currentY); - - if (group.State == BeatmapGroupState.Expanded) - { - group.Header.MoveToX(-100, 500, Easing.OutExpo); - var headerY = group.Header.Position.Y; - - foreach (BeatmapPanel panel in group.BeatmapPanels) - { - if (panel == selectedPanel) - selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; - - panel.MoveToX(-50, 500, Easing.OutExpo); - - //on first display we want to begin hidden under our group's header. - if (panel.Alpha == 0) - panel.MoveToY(headerY); - - movePanel(panel, true, animated, ref currentY); - } - } - else - { - group.Header.MoveToX(0, 500, Easing.OutExpo); - - foreach (BeatmapPanel panel in group.BeatmapPanels) - { - panel.MoveToX(0, 500, Easing.OutExpo); - movePanel(panel, false, animated, ref currentY); - } - } - } - - currentY += DrawHeight / 2; - scrollableContent.Height = currentY; - - return selectedY; - } - - private void movePanel(Panel panel, bool advance, bool animated, ref float currentY) - { - yPositions.Add(currentY); - panel.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); - - if (advance) - currentY += panel.DrawHeight + 5; - } - - private void selectGroup(BeatmapGroup group, BeatmapPanel panel = null, bool animated = true) - { - try - { - if (panel == null) - panel = group.BeatmapPanels.First(); - - if (selectedPanel == panel) return; - - Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group"); - - if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden) - selectedGroup.State = BeatmapGroupState.Collapsed; - - group.State = BeatmapGroupState.Expanded; - group.SelectedPanel = panel; - - panel.State = PanelSelectedState.Selected; - - if (selectedPanel == panel) return; - - selectedPanel = panel; - selectedGroup = group; - - SelectionChanged?.Invoke(panel.Beatmap); - } - finally - { - ScrollToSelected(animated); - } - } + public void ScrollToSelected() => scrollPositionCache.Invalidate(); protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { @@ -534,68 +351,185 @@ namespace osu.Game.Screens.Select { base.Update(); + if (!itemsCache.IsValid) + updateItems(); + + if (!scrollPositionCache.IsValid) + updateScrollPosition(); + float drawHeight = DrawHeight; - // Remove all panels that should no longer be on-screen - scrollableContent.RemoveAll(delegate(Panel p) - { - float panelPosY = p.Position.Y; - bool remove = panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !p.IsPresent; - return remove; - }); + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + drawHeight || !p.IsPresent); - // Find index range of all panels that should be on-screen - Trace.Assert(panels.Count == yPositions.Count); + // Find index range of all items that should be on-screen + Trace.Assert(Items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT); + int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(Current + drawHeight); - if (lastIndex < 0) - { - lastIndex = ~lastIndex; + if (lastIndex < 0) lastIndex = ~lastIndex; - // Add the first panel of the last visible beatmap group to preload its data. - if (lastIndex != 0 && panels[lastIndex - 1] is BeatmapSetHeader) - lastIndex++; - } + int notVisibleCount = 0; - // Add those panels within the previously found index range that should be displayed. + // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - Panel panel = panels[i]; - if (panel.State == PanelSelectedState.Hidden) + DrawableCarouselItem item = Items[i]; + + if (!item.Item.Visible) + { + if (!item.IsPresent) + notVisibleCount++; continue; + } // Only add if we're not already part of the content. - if (!scrollableContent.Contains(panel)) + if (!scrollableContent.Contains(item)) { - // Makes sure headers are always _below_ panels, + // Makes sure headers are always _below_ items, // and depth flows downward. - panel.Depth = i + (panel is BeatmapSetHeader ? panels.Count : 0); + item.Depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0); - switch (panel.LoadState) + switch (item.LoadState) { case LoadState.NotLoaded: - LoadComponentAsync(panel); + LoadComponentAsync(item); break; case LoadState.Loading: break; default: - scrollableContent.Add(panel); + scrollableContent.Add(item); break; } } } - // Update externally controlled state of currently visible panels + // this is not actually useful right now, but once we have groups may well be. + if (notVisibleCount > 50) + itemsCache.Invalidate(); + + // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). float halfHeight = drawHeight / 2; - foreach (Panel p in scrollableContent.Children) - updatePanel(p, halfHeight); + foreach (DrawableCarouselItem p in scrollableContent.Children) + updateItem(p, halfHeight); + } + + private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Beatmaps.All(b => b.Hidden)) + return null; + + // todo: remove the need for this. + foreach (var b in beatmapSet.Beatmaps) + { + if (b.Metadata == null) + b.Metadata = beatmapSet.Metadata; + } + + var set = new CarouselBeatmapSet(beatmapSet); + + foreach (var c in set.Beatmaps) + { + c.State.ValueChanged += v => + { + if (v == CarouselItemState.Selected) + { + selectedBeatmapSet = set; + SelectionChanged?.Invoke(c.Beatmap); + + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); + } + }; + } + + return set; } /// - /// Computes the x-offset of currently visible panels. Makes the carousel appear round. + /// Computes the target Y positions for every item in the carousel. + /// + /// The Y position of the currently selected item. + private void updateItems() + { + Items = root.Drawables.ToList(); + + yPositions.Clear(); + + float currentY = DrawHeight / 2; + DrawableCarouselBeatmapSet lastSet = null; + + scrollTarget = null; + + foreach (DrawableCarouselItem d in Items) + { + if (d.IsPresent) + { + switch (d) + { + case DrawableCarouselBeatmapSet set: + lastSet = set; + + set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + set.MoveToY(currentY, 750, Easing.OutExpo); + break; + case DrawableCarouselBeatmap beatmap: + if (beatmap.Item.State.Value == CarouselItemState.Selected) + scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; + + void performMove(float y, float? startY = null) + { + if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); + beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + beatmap.MoveToY(y, 750, Easing.OutExpo); + } + + Debug.Assert(lastSet != null); + + float? setY = null; + if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. + setY = lastSet.Y + lastSet.DrawHeight + 5; + + if (d.IsLoaded) + performMove(currentY, setY); + else + { + float y = currentY; + d.OnLoadComplete = _ => performMove(y, setY); + } + + break; + } + } + + yPositions.Add(currentY); + + if (d.Item.Visible) + currentY += d.DrawHeight + 5; + } + + currentY += DrawHeight / 2; + scrollableContent.Height = currentY; + + if (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected) + { + selectedBeatmapSet = null; + SelectionChanged?.Invoke(null); + } + + itemsCache.Validate(); + } + + private void updateScrollPosition() + { + if (scrollTarget != null) ScrollTo(scrollTarget.Value); + scrollPositionCache.Validate(); + } + + /// + /// Computes the x-offset of currently visible items. Makes the carousel appear round. /// /// /// Vertical distance from the center of the carousel container @@ -613,20 +547,20 @@ namespace osu.Game.Screens.Select } /// - /// Update a panel's x position and multiplicative alpha based on its y position and + /// Update a item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// - /// The panel to be updated. + /// The item to be updated. /// Half the draw height of the carousel container. - private void updatePanel(Panel p, float halfHeight) + private void updateItem(DrawableCarouselItem p, float halfHeight) { var height = p.IsPresent ? p.DrawHeight : 0; - float panelDrawY = p.Position.Y - Current + height / 2; - float dist = Math.Abs(1f - panelDrawY / halfHeight); + float itemDrawY = p.Position.Y - Current + height / 2; + float dist = Math.Abs(1f - itemDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential - // local transformation we may want to apply (e.g. when a panel gets selected, we + // local transformation we may want to apply (e.g. when a item gets selected, we // may want to smoothly transform it leftwards.) p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index a676516300..4403d412fc 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); + Leaderboard.Scope = (LeaderboardScope)tab - 1; Leaderboard.Show(); break; } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a9a778fe17..79d76dd00e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Screens.Select.Details; using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select { @@ -334,7 +335,7 @@ namespace osu.Game.Screens.Select TextSize = 14, }, }, - textFlow = new TextFlowContainer + textFlow = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -359,7 +360,7 @@ namespace osu.Game.Screens.Select private void setTextAsync(string text) { - LoadComponentAsync(new TextFlowContainer(s => s.TextSize = 14) + LoadComponentAsync(new OsuTextFlowContainer(s => s.TextSize = 14) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 5ced60a9da..fa1326de8c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Screens.Select { @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Select { private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0); - private Drawable info; + protected BufferedWedgeInfo Info; public BeatmapInfoWedge() { @@ -34,6 +35,7 @@ namespace osu.Game.Screens.Select Masking = true; BorderColour = new Color4(221, 255, 255, 255); BorderThickness = 2.5f; + Alpha = 0; EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, @@ -49,12 +51,14 @@ namespace osu.Game.Screens.Select { this.MoveToX(0, 800, Easing.OutQuint); this.RotateTo(0, 800, Easing.OutQuint); + this.FadeIn(250); } protected override void PopOut() { - this.MoveToX(-100, 800, Easing.InQuint); - this.RotateTo(10, 800, Easing.InQuint); + this.MoveToX(-100, 800, Easing.In); + this.RotateTo(10, 800, Easing.In); + this.FadeOut(500, Easing.In); } public void UpdateBeatmap(WorkingBeatmap beatmap) @@ -62,23 +66,30 @@ namespace osu.Game.Screens.Select LoadComponentAsync(new BufferedWedgeInfo(beatmap) { Shear = -Shear, - Depth = info?.Depth + 1 ?? 0, + Depth = Info?.Depth + 1 ?? 0, }, newInfo => { + State = beatmap == null ? Visibility.Hidden : Visibility.Visible; + // ensure we ourselves are visible if not already. if (!IsPresent) - this.FadeIn(250); + State = Visibility.Visible; - info?.FadeOut(250); - info?.Expire(); + Info?.FadeOut(250); + Info?.Expire(); - Add(info = newInfo); + Add(Info = newInfo); }); } public class BufferedWedgeInfo : BufferedContainer { private readonly WorkingBeatmap working; + public OsuSpriteText VersionLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } + public OsuSpriteText ArtistLabel { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } + public FillFlowContainer InfoLabelContainer { get; private set; } public BufferedWedgeInfo(WorkingBeatmap working) { @@ -88,34 +99,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - BeatmapInfo beatmapInfo = working.BeatmapInfo; - BeatmapMetadata metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Beatmap beatmap = working.Beatmap; - - List labels = new List(); - - if (beatmap != null) - { - HitObject lastObject = beatmap.HitObjects.LastOrDefault(); - double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "Length", - Icon = FontAwesome.fa_clock_o, - Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), - })); - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "BPM", - Icon = FontAwesome.fa_circle, - Content = getBPMRange(beatmap), - })); - - //get statistics from the current ruleset. - labels.AddRange(beatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s))); - } + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); PixelSnapping = true; CacheDrawnFrameBuffer = true; @@ -164,7 +149,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new OsuSpriteText + VersionLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", Text = beatmapInfo.Version, @@ -174,101 +159,160 @@ namespace osu.Game.Screens.Select }, new FillFlowContainer { - Name = "Bottom-aligned metadata", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Name = "Centre-aligned metadata", + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Y = -22, Direction = FillDirection.Vertical, Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new OsuSpriteText + TitleLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = !string.IsNullOrEmpty(metadata.Source) ? metadata.Source + " — " + metadata.Title : metadata.Title, + Text = string.IsNullOrEmpty(metadata.Source) ? metadata.Title : metadata.Source + " — " + metadata.Title, TextSize = 28, }, - new OsuSpriteText + ArtistLabel = new OsuSpriteText { Font = @"Exo2.0-MediumItalic", Text = metadata.Artist, TextSize = 17, }, - new FillFlowContainer + MapperContainer = new FillFlowContainer { Margin = new MarginPadding { Top = 10 }, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Children = new[] - { - new OsuSpriteText - { - Font = @"Exo2.0-Medium", - Text = "mapped by ", - TextSize = 15, - }, - new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = metadata.Author.Username, - TextSize = 15, - }, - } + Children = getMapper(metadata) }, - new FillFlowContainer + InfoLabelContainer = new FillFlowContainer { - Margin = new MarginPadding { Top = 20, Left = 10 }, - Spacing = new Vector2(40, 0), + Margin = new MarginPadding { Top = 20 }, + Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, - Children = labels - }, + Children = getInfoLabels() + } } - }, + } }; } + private InfoLabel[] getInfoLabels() + { + var beatmap = working.Beatmap; + var info = working.BeatmapInfo; + + List labels = new List(); + + if (beatmap?.HitObjects?.Count > 0) + { + HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "Length", + Icon = FontAwesome.fa_clock_o, + Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), + })); + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "BPM", + Icon = FontAwesome.fa_circle, + Content = getBPMRange(beatmap), + })); + + //get statistics from the current ruleset. + labels.AddRange(info.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s))); + } + + return labels.ToArray(); + } + private string getBPMRange(Beatmap beatmap) { double bpmMax = beatmap.ControlPointInfo.BPMMaximum; double bpmMin = beatmap.ControlPointInfo.BPMMinimum; - if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}bpm"; + if (Precision.AlmostEquals(bpmMin, bpmMax)) + return $"{bpmMin:0}"; - return $"{bpmMin:0}-{bpmMax:0}bpm (mostly {beatmap.ControlPointInfo.BPMMode:0}bpm)"; + return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})"; } - public class InfoLabel : Container + private OsuSpriteText[] getMapper(BeatmapMetadata metadata) { + if (string.IsNullOrEmpty(metadata.Author?.Username)) + return Array.Empty(); + + return new[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Medium", + Text = "mapped by ", + TextSize = 15, + }, + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = metadata.Author.Username, + TextSize = 15, + } + }; + } + + public class InfoLabel : Container, IHasTooltip + { + public string TooltipText { get; private set; } + public InfoLabel(BeatmapStatistic statistic) { + TooltipText = statistic.Name; AutoSizeAxes = Axes.Both; + Children = new Drawable[] { - new SpriteIcon + new Container { - Icon = FontAwesome.fa_square, - Origin = Anchor.Centre, - Colour = new Color4(68, 17, 136, 255), - Rotation = 45, - Size = new Vector2(20), - }, - new SpriteIcon - { - Icon = statistic.Icon, - Origin = Anchor.Centre, - Colour = new Color4(255, 221, 85, 255), - Scale = new Vector2(0.8f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Size = new Vector2(20), + Children = new[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"441288"), + Icon = FontAwesome.fa_square, + Rotation = 45, + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Colour = OsuColour.FromHex(@"f7dd55"), + Icon = statistic.Icon, + }, + } }, new OsuSpriteText { - Margin = new MarginPadding { Left = 13 }, - Font = @"Exo2.0-Bold", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Colour = new Color4(255, 221, 85, 255), + Font = @"Exo2.0-Bold", + Margin = new MarginPadding { Left = 30 }, Text = statistic.Content, TextSize = 17, - Origin = Anchor.CentreLeft - }, + } }; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs new file mode 100644 index 0000000000..d7e7b1e265 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselBeatmap : CarouselItem + { + public readonly BeatmapInfo Beatmap; + + public CarouselBeatmap(BeatmapInfo beatmap) + { + Beatmap = beatmap; + State.Value = CarouselItemState.Collapsed; + } + + protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + + bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps; + + if (!string.IsNullOrEmpty(criteria.SearchText)) + match &= + Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0) || + Beatmap.Version.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0; + + Filtered.Value = !match; + } + + public override int CompareTo(FilterCriteria criteria, CarouselItem other) + { + if (!(other is CarouselBeatmap otherBeatmap)) + return base.CompareTo(criteria, other); + + switch (criteria.Sort) + { + default: + case SortMode.Difficulty: + var ruleset = Beatmap.RulesetID.CompareTo(otherBeatmap.Beatmap.RulesetID); + if (ruleset != 0) return ruleset; + + return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); + } + } + + public override string ToString() => Beatmap.ToString(); + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs new file mode 100644 index 0000000000..885595fc51 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselBeatmapSet : CarouselGroupEagerSelect + { + public IEnumerable Beatmaps => InternalChildren.OfType(); + + public BeatmapSetInfo BeatmapSet; + + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet) + { + BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); + + beatmapSet.Beatmaps + .Where(b => !b.Hidden) + .Select(b => new CarouselBeatmap(b)) + .ForEach(AddChild); + } + + protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); + + public override int CompareTo(FilterCriteria criteria, CarouselItem other) + { + if (!(other is CarouselBeatmapSet otherSet)) + return base.CompareTo(criteria, other); + + switch (criteria.Sort) + { + default: + case SortMode.Artist: + return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Title: + return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Author: + return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Difficulty: + return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); + } + } + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + Filtered.Value = InternalChildren.All(i => i.Filtered); + } + + public override string ToString() => BeatmapSet.ToString(); + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs new file mode 100644 index 0000000000..a54eeb562e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -0,0 +1,91 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Screens.Select.Carousel +{ + /// + /// A group which ensures only one child is selected. + /// + public class CarouselGroup : CarouselItem + { + private readonly List items; + + protected override DrawableCarouselItem CreateDrawableRepresentation() => null; + + public IReadOnlyList Children => InternalChildren; + + protected List InternalChildren = new List(); + + public override List Drawables + { + get + { + var drawables = base.Drawables; + foreach (var c in InternalChildren) + drawables.AddRange(c.Drawables); + return drawables; + } + } + + public virtual void RemoveChild(CarouselItem i) + { + InternalChildren.Remove(i); + + // it's important we do the deselection after removing, so any further actions based on + // State.ValueChanged make decisions post-removal. + i.State.Value = CarouselItemState.Collapsed; + } + + public virtual void AddChild(CarouselItem i) + { + i.State.ValueChanged += v => ChildItemStateChanged(i, v); + InternalChildren.Add(i); + } + + public CarouselGroup(List items = null) + { + if (items != null) InternalChildren = items; + + State.ValueChanged += v => + { + switch (v) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + break; + case CarouselItemState.Selected: + InternalChildren.ForEach(c => + { + if (c.State == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; + }); + break; + } + }; + } + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + InternalChildren.Sort((x, y) => x.CompareTo(criteria, y)); + InternalChildren.ForEach(c => c.Filter(criteria)); + } + + protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) + { + // ensure we are the only item selected + if (value == CarouselItemState.Selected) + { + foreach (var b in InternalChildren) + { + if (item == b) continue; + b.State.Value = CarouselItemState.NotSelected; + } + + State.Value = CarouselItemState.Selected; + } + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs new file mode 100644 index 0000000000..5701760221 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -0,0 +1,104 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; + +namespace osu.Game.Screens.Select.Carousel +{ + /// + /// A group which ensures at least one child is selected (if the group itself is selected). + /// + public class CarouselGroupEagerSelect : CarouselGroup + { + public CarouselGroupEagerSelect() + { + State.ValueChanged += v => + { + if (v == CarouselItemState.Selected) + attemptSelection(); + }; + } + + /// + /// We need to keep track of the index for cases where the selection is removed but we want to select a new item based on its old location. + /// + private int lastSelectedIndex; + + private CarouselItem lastSelected; + + /// + /// To avoid overhead during filter operations, we don't attempt any selections until after all + /// children have been filtered. This bool will be true during the base + /// operation. + /// + private bool filteringChildren; + + public override void Filter(FilterCriteria criteria) + { + filteringChildren = true; + base.Filter(criteria); + filteringChildren = false; + + attemptSelection(); + } + + public override void RemoveChild(CarouselItem i) + { + base.RemoveChild(i); + + if (i != lastSelected) + updateSelectedIndex(); + } + + public override void AddChild(CarouselItem i) + { + base.AddChild(i); + attemptSelection(); + } + + protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) + { + base.ChildItemStateChanged(item, value); + + switch (value) + { + case CarouselItemState.Selected: + updateSelected(item); + break; + case CarouselItemState.NotSelected: + case CarouselItemState.Collapsed: + attemptSelection(); + break; + } + } + + private void attemptSelection() + { + if (filteringChildren) return; + + // we only perform eager selection if we are a currently selected group. + if (State != CarouselItemState.Selected) return; + + // we only perform eager selection if none of our children are in a selected state already. + if (Children.Any(i => i.State == CarouselItemState.Selected)) return; + + CarouselItem nextToSelect = + Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered) ?? + Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered); + + if (nextToSelect != null) + nextToSelect.State.Value = CarouselItemState.Selected; + else + updateSelected(null); + } + + private void updateSelected(CarouselItem newSelection) + { + lastSelected = newSelection; + updateSelectedIndex(); + } + + private void updateSelectedIndex() => lastSelectedIndex = lastSelected == null ? 0 : Math.Max(0, InternalChildren.IndexOf(lastSelected)); + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs new file mode 100644 index 0000000000..7d76aee253 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2017 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.Configuration; + +namespace osu.Game.Screens.Select.Carousel +{ + public abstract class CarouselItem + { + public readonly BindableBool Filtered = new BindableBool(); + + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + + /// + /// This item is not in a hidden state. + /// + public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered; + + public virtual List Drawables + { + get + { + var items = new List(); + + var self = drawableRepresentation.Value; + if (self?.IsPresent == true) items.Add(self); + + return items; + } + } + + protected CarouselItem() + { + drawableRepresentation = new Lazy(CreateDrawableRepresentation); + + Filtered.ValueChanged += v => + { + if (v && State == CarouselItemState.Selected) + State.Value = CarouselItemState.NotSelected; + }; + } + + private readonly Lazy drawableRepresentation; + + protected abstract DrawableCarouselItem CreateDrawableRepresentation(); + + public virtual void Filter(FilterCriteria criteria) + { + } + + public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => GetHashCode().CompareTo(other.GetHashCode()); + } + + public enum CarouselItemState + { + Collapsed, + NotSelected, + Selected, + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs similarity index 77% rename from osu.Game/Beatmaps/Drawables/BeatmapPanel.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e6bf08eb9f..6c0cc341fd 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -2,82 +2,56 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class BeatmapPanel : Panel, IHasContextMenu + public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public BeatmapInfo Beatmap; - private readonly Sprite background; + private readonly BeatmapInfo beatmap; - public Action GainedSelection; - public Action StartRequested; - public Action EditRequested; - public Action HideRequested; + private Sprite background; - private readonly Triangles triangles; - private readonly StarCounter starCounter; + private Action startRequested; + private Action editRequested; + private Action hideRequested; - protected override void Selected() + private Triangles triangles; + private StarCounter starCounter; + + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { - base.Selected(); - - GainedSelection?.Invoke(this); - - background.Colour = ColourInfo.GradientVertical( - new Color4(20, 43, 51, 255), - new Color4(40, 86, 102, 255)); - - triangles.Colour = Color4.White; - } - - protected override void Deselected() - { - base.Deselected(); - - background.Colour = new Color4(20, 43, 51, 255); - triangles.Colour = OsuColour.Gray(0.5f); - } - - protected override bool OnClick(InputState state) - { - if (State == PanelSelectedState.Selected) - StartRequested?.Invoke(this); - - return base.OnClick(state); - } - - protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) - { - if (!IsLoaded) return; - - base.ApplyState(last); - - if (last == PanelSelectedState.Hidden && State != last) - starCounter.ReplayAnimation(); - } - - public BeatmapPanel(BeatmapInfo beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - Beatmap = beatmap; + beatmap = panel.Beatmap; Height *= 0.60f; + } + + [BackgroundDependencyLoader(true)] + private void load(SongSelect songSelect, BeatmapManager manager) + { + if (songSelect != null) + { + startRequested = songSelect.Start; + editRequested = songSelect.Edit; + } + + if (manager != null) + hideRequested = manager.Hide; Children = new Drawable[] { @@ -157,11 +131,46 @@ namespace osu.Game.Beatmaps.Drawables }; } + protected override void Selected() + { + base.Selected(); + + background.Colour = ColourInfo.GradientVertical( + new Color4(20, 43, 51, 255), + new Color4(40, 86, 102, 255)); + + triangles.Colour = Color4.White; + } + + protected override void Deselected() + { + base.Deselected(); + + background.Colour = new Color4(20, 43, 51, 255); + triangles.Colour = OsuColour.Gray(0.5f); + } + + protected override bool OnClick(InputState state) + { + if (Item.State == CarouselItemState.Selected) + startRequested?.Invoke(beatmap); + + return base.OnClick(state); + } + + protected override void ApplyState() + { + if (Item.State.Value != CarouselItemState.Collapsed && Alpha == 0) + starCounter.ReplayAnimation(); + + base.ApplyState(); + } + public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)), + new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), }; } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs similarity index 61% rename from osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 85daefea57..4b999f9b87 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,67 +4,61 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class BeatmapSetHeader : Panel, IHasContextMenu + public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { - public Action GainedSelection; + private Action deleteRequested; + private Action restoreHiddenRequested; + private Action viewDetails; - public Action DeleteRequested; - - public Action RestoreHiddenRequested; - - private readonly WorkingBeatmap beatmap; + private readonly BeatmapSetInfo beatmapSet; private readonly FillFlowContainer difficultyIcons; - public BeatmapSetHeader(WorkingBeatmap beatmap) + public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) + : base(set) { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - this.beatmap = beatmap; - - difficultyIcons = new FillFlowContainer - { - Margin = new MarginPadding { Top = 5 }, - AutoSizeAxes = Axes.Both, - }; + beatmapSet = set.BeatmapSet; } - protected override void Selected() - { - base.Selected(); - GainedSelection?.Invoke(this); - } - - [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + [BackgroundDependencyLoader(true)] + private void load(LocalisationEngine localisation, BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) { if (localisation == null) throw new ArgumentNullException(nameof(localisation)); + restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); + deleteRequested = manager.Delete; + if (beatmapOverlay != null) + viewDetails = beatmapOverlay.ShowBeatmapSet; + Children = new Drawable[] { new DelayedLoadWrapper( - new PanelBackground(beatmap) + new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, 300), + OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), + }, 300 + ), new FillFlowContainer { Direction = FillDirection.Vertical, @@ -75,23 +69,49 @@ namespace osu.Game.Beatmaps.Drawables new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title), + Current = localisation.GetUnicodePreference(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), TextSize = 22, Shadow = true, }, new OsuSpriteText { Font = @"Exo2.0-SemiBoldItalic", - Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + Current = localisation.GetUnicodePreference(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), TextSize = 17, Shadow = true, }, - difficultyIcons + new FillFlowContainer + { + Margin = new MarginPadding { Top = 5 }, + AutoSizeAxes = Axes.Both, + Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() + } } } }; } + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (Item.State == CarouselItemState.NotSelected) + items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); + + if (beatmapSet.OnlineBeatmapSetID != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails?.Invoke(beatmapSet.OnlineBeatmapSetID.Value))); + + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested?.Invoke(beatmapSet))); + + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => deleteRequested?.Invoke(beatmapSet))); + + return items.ToArray(); + } + } + private class PanelBackground : BufferedContainer { public PanelBackground(WorkingBeatmap working) @@ -128,22 +148,19 @@ namespace osu.Game.Beatmaps.Drawables new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, }, } @@ -152,30 +169,16 @@ namespace osu.Game.Beatmaps.Drawables } } - public void AddDifficultyIcons(IEnumerable panels) + public class FilterableDifficultyIcon : DifficultyIcon { - if (panels == null) - throw new ArgumentNullException(nameof(panels)); + private readonly BindableBool filtered = new BindableBool(); - foreach (var p in panels) - difficultyIcons.Add(new DifficultyIcon(p.Beatmap)); - } - - public MenuItem[] ContextMenuItems - { - get + public FilterableDifficultyIcon(CarouselBeatmap item) + : base(item.Beatmap) { - List items = new List(); - - if (State == PanelSelectedState.NotSelected) - items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected)); - - if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo))); - - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo))); - - return items.ToArray(); + filtered.BindTo(item.Filtered); + filtered.ValueChanged += v => Schedule(() => this.FadeTo(v ? 0.1f : 1, 100)); + filtered.TriggerChange(); } } } diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs similarity index 68% rename from osu.Game/Beatmaps/Drawables/Panel.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c990a0ea46..cb354b3602 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,45 +1,53 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using osu.Framework; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.MathUtils; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class Panel : Container, IStateful + public abstract class DrawableCarouselItem : Container { public const float MAX_HEIGHT = 80; - public event Action StateChanged; - public override bool RemoveWhenNotAlive => false; - private readonly Container nestedContainer; + public override bool IsPresent => base.IsPresent || Item.Visible; - private readonly Container borderContainer; + public readonly CarouselItem Item; - private readonly Box hoverLayer; + private Container nestedContainer; + private Container borderContainer; + + private Box hoverLayer; protected override Container Content => nestedContainer; - protected Panel() + protected DrawableCarouselItem(CarouselItem item) { + Item = item; + Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; + Alpha = 0; + } - AddInternal(borderContainer = new Container + private SampleChannel sampleHover; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + InternalChild = borderContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -58,16 +66,8 @@ namespace osu.Game.Beatmaps.Drawables Blending = BlendingMode.Additive, }, } - }); + }; - Alpha = 0; - } - - private SampleChannel sampleHover; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -86,60 +86,41 @@ namespace osu.Game.Beatmaps.Drawables base.OnHoverLost(state); } - public void SetMultiplicativeAlpha(float alpha) - { - borderContainer.Alpha = alpha; - } + public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha; protected override void LoadComplete() { base.LoadComplete(); + ApplyState(); + Item.Filtered.ValueChanged += _ => Schedule(ApplyState); + Item.State.ValueChanged += _ => Schedule(ApplyState); } - protected virtual void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) + protected virtual void ApplyState() { if (!IsLoaded) return; - switch (state) + switch (Item.State.Value) { - case PanelSelectedState.Hidden: - case PanelSelectedState.NotSelected: + case CarouselItemState.NotSelected: Deselected(); break; - case PanelSelectedState.Selected: + case CarouselItemState.Selected: Selected(); break; } - if (state == PanelSelectedState.Hidden) + if (!Item.Visible) this.FadeOut(300, Easing.OutQuint); else this.FadeIn(250); } - private PanelSelectedState state = PanelSelectedState.NotSelected; - - public PanelSelectedState State - { - get { return state; } - - set - { - if (state == value) - return; - - var last = state; - state = value; - - ApplyState(last); - - StateChanged?.Invoke(State); - } - } - protected virtual void Selected() { + Item.State.Value = CarouselItemState.Selected; + borderContainer.BorderThickness = 2.5f; borderContainer.EdgeEffect = new EdgeEffectParameters { @@ -152,6 +133,8 @@ namespace osu.Game.Beatmaps.Drawables protected virtual void Deselected() { + Item.State.Value = CarouselItemState.NotSelected; + borderContainer.BorderThickness = 0; borderContainer.EdgeEffect = new EdgeEffectParameters { @@ -164,15 +147,8 @@ namespace osu.Game.Beatmaps.Drawables protected override bool OnClick(InputState state) { - State = PanelSelectedState.Selected; + Item.State.Value = CarouselItemState.Selected; return true; } } - - public enum PanelSelectedState - { - Hidden, - NotSelected, - Selected - } } diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index 907c080729..f02d25501e 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -1,14 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; - namespace osu.Game.Screens.Select { public class EditSongSelect : SongSelect { protected override bool ShowFooter => false; - protected override void OnSelected(InputState state) => Exit(); + protected override void Start() => Exit(); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 1b86cec613..2dbd63f77b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Select { Children = new Drawable[] { - new Box + Background = new Box { Colour = Color4.Black, Alpha = 0.8f, @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Select private Bindable showConverted; + public readonly Box Background; + [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, OsuConfigManager config) { diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c1355bfa63..8e99e29c1f 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,10 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps.Drawables; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -17,47 +13,5 @@ namespace osu.Game.Screens.Select public string SearchText; public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; - - public void Filter(List groups) - { - foreach (var g in groups) - { - var set = g.BeatmapSet; - - bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); - - bool match = hasCurrentMode; - - if (!string.IsNullOrEmpty(SearchText)) - match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); - - switch (g.State) - { - case BeatmapGroupState.Hidden: - if (match) g.State = BeatmapGroupState.Collapsed; - break; - default: - if (!match) g.State = BeatmapGroupState.Hidden; - break; - } - } - - switch (Sort) - { - default: - case SortMode.Artist: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Title: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Author: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author.Username, y.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Difficulty: - groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); - break; - } - } } } diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs new file mode 100644 index 0000000000..489ab79fa4 --- /dev/null +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Select +{ + public class ImportFromStablePopup : PopupDialog + { + public ImportFromStablePopup(Action importFromStable) + { + HeaderText = @"You have no beatmaps!"; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?"; + + Icon = FontAwesome.fa_trash_o; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes please!", + Action = importFromStable + }, + new PopupDialogCancelButton + { + Text = @"No, I'd like to start from scratch", + }, + }; + } + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index d896da5319..c15a179e8c 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -18,14 +18,23 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Logging; +using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Leaderboards { public class Leaderboard : Container { + private const double fade_duration = 300; + private readonly ScrollContainer scrollContainer; + private readonly Container placeholderContainer; + private FillFlowContainer scrollFlow; + private readonly Bindable ruleset = new Bindable(); + public Action ScoreSelected; private readonly LoadingAnimation loading; @@ -38,15 +47,18 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; - getScoresRequest?.Cancel(); - scrollFlow?.FadeOut(200); - scrollFlow?.Expire(); + scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - if (scores == null) + loading.Hide(); + + if (scores == null || !scores.Any()) return; + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; + // schedule because we may not be loaded yet (LoadComponentAsync complains). Schedule(() => { @@ -74,6 +86,56 @@ namespace osu.Game.Screens.Select.Leaderboards } } + private LeaderboardScope scope; + + public LeaderboardScope Scope + { + get { return scope; } + set + { + if (value == scope) return; + + scope = value; + updateScores(); + } + } + + private PlaceholderState placeholderState; + + protected PlaceholderState PlaceholderState + { + get { return placeholderState; } + set + { + if (value == placeholderState) return; + + switch (placeholderState = value) + { + case PlaceholderState.NetworkFailure: + replacePlaceholder(new RetrievalFailurePlaceholder + { + OnRetry = updateScores, + }); + break; + case PlaceholderState.Unavailable: + replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); + break; + case PlaceholderState.NoScores: + replacePlaceholder(new MessagePlaceholder(@"No records yet!")); + break; + case PlaceholderState.NotLoggedIn: + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + break; + case PlaceholderState.NotSupporter: + replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); + break; + default: + replacePlaceholder(null); + break; + } + } + } + public Leaderboard() { Children = new Drawable[] @@ -83,13 +145,17 @@ namespace osu.Game.Screens.Select.Leaderboards RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, }, - loading = new LoadingAnimation() + loading = new LoadingAnimation(), + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, }; } private APIAccess api; - private BeatmapInfo beatmap; + private OsuGame osuGame; private ScheduledDelegate pendingBeatmapSwitch; @@ -109,33 +175,114 @@ namespace osu.Game.Screens.Select.Leaderboards } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api) + private void load(APIAccess api, OsuGame osuGame) { this.api = api; + this.osuGame = osuGame; + + if (osuGame != null) + ruleset.BindTo(osuGame.Ruleset); + + ruleset.ValueChanged += r => updateScores(); + + if (api != null) + api.OnStateChange += handleApiStateChange; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (api != null) + api.OnStateChange -= handleApiStateChange; } private GetScoresRequest getScoresRequest; + private void handleApiStateChange(APIState oldState, APIState newState) + { + if (Scope == LeaderboardScope.Local) + // No need to respond to API state change while current scope is local + return; + + if (newState == APIState.Online) + updateScores(); + } + private void updateScores() { - if (!IsLoaded) return; - - Scores = null; getScoresRequest?.Cancel(); + getScoresRequest = null; + Scores = null; - if (api == null || Beatmap?.OnlineBeatmapID == null) return; + if (Scope == LeaderboardScope.Local) + { + // TODO: get local scores from wherever here. + PlaceholderState = PlaceholderState.NoScores; + return; + } + if (Beatmap?.OnlineBeatmapID == null) + { + PlaceholderState = PlaceholderState.Unavailable; + return; + } + + if (api?.IsLoggedIn != true) + { + PlaceholderState = PlaceholderState.NotLoggedIn; + return; + } + + PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap); + if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + loading.Hide(); + PlaceholderState = PlaceholderState.NotSupporter; + return; + } + + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest.Success += r => { Scores = r.Scores; - loading.Hide(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; }; + getScoresRequest.Failure += onUpdateFailed; + api.Queue(getScoresRequest); } + private void onUpdateFailed(Exception e) + { + if (e is OperationCanceledException) return; + + PlaceholderState = PlaceholderState.NetworkFailure; + Logger.Error(e, @"Couldn't fetch beatmap scores!"); + } + + private void replacePlaceholder(Placeholder placeholder) + { + var existingPlaceholder = placeholderContainer.Children.LastOrDefault() as Placeholder; + + if (placeholder != null && placeholder.Equals(existingPlaceholder)) + return; + + existingPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + + if (placeholder == null) + return; + + Scores = null; + + placeholderContainer.Add(placeholder); + + placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); + placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + } + protected override void Update() { base.Update(); @@ -165,4 +312,23 @@ namespace osu.Game.Screens.Select.Leaderboards } } } + + public enum LeaderboardScope + { + Local, + Country, + Global, + Friend, + } + + public enum PlaceholderState + { + Successful, + Retrieving, + NetworkFailure, + Unavailable, + NoScores, + NotLoggedIn, + NotSupporter, + } } diff --git a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs new file mode 100644 index 0000000000..4f94087d30 --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Screens.Select.Leaderboards +{ + public class MessagePlaceholder : Placeholder + { + private readonly string message; + + public MessagePlaceholder(string message) + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_exclamation_circle, + Size = new Vector2(26), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Text = this.message = message, + TextSize = 22, + }, + }; + } + + public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message; + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs new file mode 100644 index 0000000000..8427259106 --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Select.Leaderboards +{ + public abstract class Placeholder : FillFlowContainer, IEquatable + { + protected Placeholder() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + public virtual bool Equals(Placeholder other) => GetType() == other?.GetType(); + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs new file mode 100644 index 0000000000..7563c08c8b --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Screens.Select.Leaderboards +{ + public class RetrievalFailurePlaceholder : Placeholder + { + public Action OnRetry; + + public RetrievalFailurePlaceholder() + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new RetryButton + { + Action = () => OnRetry?.Invoke(), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"Couldn't retrieve scores!", + TextSize = 22, + }, + }; + } + + public class RetryButton : OsuHoverContainer + { + private readonly SpriteIcon icon; + + public Action Action; + + public RetryButton() + { + Height = 26; + Width = 26; + Child = new OsuClickableContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => Action?.Invoke(), + Child = icon = new SpriteIcon + { + Icon = FontAwesome.fa_refresh, + Size = new Vector2(26), + Shadow = true, + }, + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + icon.ScaleTo(0.8f, 4000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + icon.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 2d3b198478..898c195432 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -1,12 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; - namespace osu.Game.Screens.Select { public class MatchSongSelect : SongSelect { - protected override void OnSelected(InputState state) => Exit(); + protected override void Start() => Exit(); } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index bba6ddf577..727cdb9959 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -8,10 +8,10 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; @@ -46,8 +46,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleConfirm; - [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay) { sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); @@ -60,6 +60,16 @@ namespace osu.Game.Screens.Select ValidForResume = false; Push(new Editor()); }, Key.Number3); + + if (dialogOverlay != null) + { + Schedule(() => + { + // if we have no beatmaps but osu-stable is found, let's prompt the user to import. + if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable) + dialogOverlay.Push(new ImportFromStablePopup(() => beatmaps.ImportFromStable())); + }); + } } protected override void UpdateBeatmap(WorkingBeatmap beatmap) @@ -114,11 +124,12 @@ namespace osu.Game.Screens.Select return false; } - protected override void OnSelected(InputState state) + protected override void Start() { if (player != null) return; - if (state?.Keyboard.ControlPressed == true) + // Ctrl+Enter should start map with autoplay enabled. + if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto.GetType(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6fcaff7976..9e5a2fa633 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -27,25 +27,11 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { - private BeatmapManager beatmaps; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); - - private readonly BeatmapCarousel carousel; - private DialogOverlay dialogOverlay; - private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - + private static readonly Vector2 background_blur = new Vector2(20); private const float left_area_padding = 20; - private readonly BeatmapInfoWedge beatmapInfoWedge; - - protected Container LeftContent; - - private static readonly Vector2 background_blur = new Vector2(20); - private CancellationTokenSource initialAddSetsTask; - - private SampleChannel sampleChangeDifficulty; - private SampleChannel sampleChangeBeatmap; + public readonly FilterControl FilterControl; protected virtual bool ShowFooter => true; @@ -65,77 +51,109 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - public readonly FilterControl FilterControl; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); + + protected Container LeftContent; + + protected readonly BeatmapCarousel Carousel; + private readonly BeatmapInfoWedge beatmapInfoWedge; + private DialogOverlay dialogOverlay; + private BeatmapManager beatmaps; + + private SampleChannel sampleChangeDifficulty; + private SampleChannel sampleChangeBeatmap; + + private CancellationTokenSource initialAddSetsTask; + + private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); protected SongSelect() { const float carousel_width = 640; const float filter_height = 100; - Add(new ParallaxContainer + AddRange(new Drawable[] { - Padding = new MarginPadding { Top = filter_height }, - ParallaxAmount = 0.005f, - RelativeSizeAxes = Axes.Both, - Children = new[] + new ParallaxContainer { - new WedgeBackground + Padding = new MarginPadding { Top = filter_height }, + ParallaxAmount = 0.005f, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new WedgeBackground + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = carousel_width * 0.76f }, + } + } + }, + LeftContent = new Container + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(wedged_container_size.X, 1), + Padding = new MarginPadding + { + Bottom = 50, + Top = wedged_container_size.Y + left_area_padding, + Left = left_area_padding, + Right = left_area_padding * 2, + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 2, //avoid horizontal masking so the panels don't clip when screen stack is pushed. + Child = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, - } - } - }); - Add(LeftContent = new Container - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(wedged_container_size.X, 1), - Padding = new MarginPadding - { - Bottom = 50, - Top = wedged_container_size.Y + left_area_padding, - Left = left_area_padding, - Right = left_area_padding * 2, - } - }); - Add(carousel = new BeatmapCarousel - { - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - SelectionChanged = carouselSelectionChanged, - BeatmapsChanged = carouselBeatmapsLoaded, - DeleteRequested = promptDelete, - RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); }, - EditRequested = editRequested, - HideDifficultyRequested = b => beatmaps.Hide(b), - StartRequested = () => carouselRaisedStart(), - }); - Add(FilterControl = new FilterControl - { - RelativeSizeAxes = Axes.X, - Height = filter_height, - FilterChanged = criteria => filterChanged(criteria), - Exit = Exit, - }); - Add(beatmapInfoWedge = new BeatmapInfoWedge - { - Alpha = 0, - Size = wedged_container_size, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding - { - Top = left_area_padding, - Right = left_area_padding, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + Children = new Drawable[] + { + Carousel = new BeatmapCarousel + { + Masking = false, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(carousel_width, 1), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + SelectionChanged = carouselSelectionChanged, + BeatmapSetsChanged = carouselBeatmapsLoaded, + }, + FilterControl = new FilterControl + { + RelativeSizeAxes = Axes.X, + Height = filter_height, + FilterChanged = c => Carousel.Filter(c), + Background = { Width = 2 }, + Exit = Exit, + }, + } + }, }, - }); - Add(new ResetScrollContainer(() => carousel.ScrollToSelected()) - { - RelativeSizeAxes = Axes.Y, - Width = 250, + beatmapInfoWedge = new BeatmapInfoWedge + { + Size = wedged_container_size, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding + { + Top = left_area_padding, + Right = left_area_padding, + }, + }, + new ResetScrollContainer(() => Carousel.ScrollToSelected()) + { + RelativeSizeAxes = Axes.Y, + Width = 250, + } }); if (ShowFooter) @@ -163,12 +181,14 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { + dependencies.Cache(this); + if (Footer != null) { Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); } if (this.beatmaps == null) @@ -189,36 +209,31 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); - carousel.Beatmaps = this.beatmaps.GetAllUsableBeatmapSets(); + Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); - Beatmap.ValueChanged += beatmap_ValueChanged; + Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; + Beatmap.TriggerChange(); - Beatmap.DisabledChanged += disabled => carousel.AllowSelection = !disabled; - carousel.AllowSelection = !Beatmap.Disabled; + Beatmap.ValueChanged += b => + { + if (IsCurrentScreen) + Carousel.SelectBeatmap(b?.BeatmapInfo); + }; } - private void editRequested(BeatmapInfo beatmap) + public void Edit(BeatmapInfo beatmap) { Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); Push(new Editor()); } - private void onBeatmapRestored(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b)); - private void onBeatmapHidden(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b)); - - private void carouselBeatmapsLoaded() - { - if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) - carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false); - else - carousel.SelectNextRandom(); - } - - private void carouselRaisedStart(InputState state = null) + public void Start(BeatmapInfo beatmap) { // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). - carousel.FlushPendingFilters(); + Carousel.FlushPendingFilterOperations(); + + Carousel.SelectBeatmap(beatmap); if (selectionChangedDebounce?.Completed == false) { @@ -227,9 +242,14 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - OnSelected(state); + Start(); } + /// + /// Called when a selection is made. + /// + protected abstract void Start(); + private ScheduledDelegate selectionChangedDebounce; // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. @@ -246,7 +266,7 @@ namespace osu.Game.Screens.Select // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (beatmap?.Equals(Beatmap.Value.BeatmapInfo) != true) { - bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value.BeatmapInfo.BeatmapSetInfoID; + bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); ensurePlayingSelected(preview); @@ -255,18 +275,15 @@ namespace osu.Game.Screens.Select UpdateBeatmap(Beatmap.Value); }; - selectionChangedDebounce?.Cancel(); - if (beatmap?.Equals(beatmapNoDebounce) == true) return; + selectionChangedDebounce?.Cancel(); + beatmapNoDebounce = beatmap; if (beatmap == null) - { - if (!Beatmap.IsDefault) - performLoad(); - } + performLoad(); else { if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) @@ -277,35 +294,23 @@ namespace osu.Game.Screens.Select if (beatmap == Beatmap.Value.BeatmapInfo) performLoad(); else - selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 100); + selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 200); } } private void triggerRandom() { if (GetContainingInputManager().CurrentState.Keyboard.ShiftPressed) - carousel.SelectPreviousRandom(); + Carousel.SelectPreviousRandom(); else - carousel.SelectNextRandom(); + Carousel.SelectNextRandom(); } - protected abstract void OnSelected(InputState state); - - private void filterChanged(FilterCriteria criteria, bool debounce = true) - { - carousel.Filter(criteria, debounce); - } - - private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => addBeatmapSet(s)); - - private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); - protected override void OnEntering(Screen last) { base.OnEntering(last); Content.FadeInFromZero(250); - FilterControl.Activate(); } @@ -334,7 +339,7 @@ namespace osu.Game.Screens.Select logo.Action = () => { - carouselRaisedStart(); + Start(); return false; }; } @@ -346,13 +351,6 @@ namespace osu.Game.Screens.Select logo.FadeOut(logo_transition / 2, Easing.Out); } - private void beatmap_ValueChanged(WorkingBeatmap beatmap) - { - if (!IsCurrentScreen) return; - - carousel.SelectBeatmap(beatmap?.BeatmapInfo); - } - protected override void OnResuming(Screen last) { if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) @@ -413,15 +411,13 @@ namespace osu.Game.Screens.Select /// The working beatmap. protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) { - var backgroundModeBeatmap = Background as BackgroundScreenBeatmap; - if (backgroundModeBeatmap != null) + if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint); backgroundModeBeatmap.FadeTo(1, 250); } - beatmapInfoWedge.State = Visibility.Visible; beatmapInfoWedge.UpdateBeatmap(beatmap); } @@ -439,20 +435,22 @@ namespace osu.Game.Screens.Select } } - private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet); + private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); + private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); + private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - private void removeBeatmapSet(BeatmapSetInfo beatmapSet) + private void carouselBeatmapsLoaded() { - carousel.RemoveBeatmap(beatmapSet); - if (carousel.SelectedBeatmap == null) - Beatmap.SetDefault(); + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false) + Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); + else if (Carousel.SelectedBeatmapSet == null) + Carousel.SelectNextRandom(); } - private void promptDelete(BeatmapSetInfo beatmap) + private void delete(BeatmapSetInfo beatmap) { - if (beatmap == null) - return; - + if (beatmap == null) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } @@ -464,15 +462,16 @@ namespace osu.Game.Screens.Select { case Key.KeypadEnter: case Key.Enter: - carouselRaisedStart(state); + Start(); return true; case Key.Delete: if (state.Keyboard.ShiftPressed) { if (!Beatmap.IsDefault) - promptDelete(Beatmap.Value.BeatmapSetInfo); + delete(Beatmap.Value.BeatmapSetInfo); return true; } + break; } diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 332a6f79cb..c6d9202121 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -6,6 +6,7 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; namespace osu.Game.Storyboards { @@ -23,6 +24,7 @@ namespace osu.Game.Storyboards public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); + [JsonIgnore] public IEnumerable Timelines { get @@ -39,14 +41,25 @@ namespace osu.Game.Storyboards } } + [JsonIgnore] public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime); + + [JsonIgnore] public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime); + + [JsonIgnore] public double CommandsDuration => CommandsEndTime - CommandsStartTime; + [JsonIgnore] public virtual double StartTime => CommandsStartTime; + + [JsonIgnore] public virtual double EndTime => CommandsEndTime; + + [JsonIgnore] public double Duration => EndTime - StartTime; + [JsonIgnore] public bool HasCommands => Timelines.Any(t => t.HasCommands); public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 59cbe74650..4eca910c1e 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -5,10 +5,11 @@ using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; +using System; namespace osu.Game.Storyboards { - public class Storyboard + public class Storyboard : IDisposable { private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; @@ -59,5 +60,29 @@ namespace osu.Game.Storyboards } return drawable; } + + #region Disposal + + ~Storyboard() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + isDisposed = true; + } + + #endregion } } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 6da14e9b12..8984fc843f 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; @@ -224,7 +223,7 @@ namespace osu.Game.Tests.Visual if (!api.IsLoggedIn) { - InternalChild = new SpriteText + InternalChild = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -244,7 +243,7 @@ namespace osu.Game.Tests.Visual if (!api.IsLoggedIn) return; - lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo); + lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset); lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap))); api.Queue(lastRequest); } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index d9951e002b..106f0fa8f3 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -8,7 +8,6 @@ using System.Text; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -63,7 +62,7 @@ namespace osu.Game.Tests.Visual using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) using (var reader = new StreamReader(stream)) - beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader); + beatmap = Game.Beatmaps.Formats.Decoder.GetDecoder(reader).DecodeBeatmap(reader); return beatmap; } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2adb809334..5c0e5f1f95 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -75,7 +75,7 @@ namespace osu.Game.Users public bool Active; [JsonProperty(@"interests")] - public string Intrerests; + public string Interests; [JsonProperty(@"occupation")] public string Occupation; @@ -136,5 +136,7 @@ namespace osu.Game.Users [JsonProperty(@"rankHistory")] public RankHistoryData RankHistory; + + public override string ToString() => Username; } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d056afcf54..a2cc8e8d49 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,8 +16,8 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Profile; namespace osu.Game.Users { @@ -220,53 +220,5 @@ namespace osu.Game.Users { new OsuMenuItem("View Profile", MenuItemType.Highlighted, ViewProfile), }; - - private class SupporterIcon : CircularContainer - { - private readonly Box background; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 76929dcbb3..20007e3306 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -172,6 +172,7 @@ NEXT_LINE NEXT_LINE True + NEVER False False True @@ -602,6 +603,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> @@ -654,7 +656,11 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True + True + True + True True True + True True True