diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000000..95f993e510 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index b26a895788..21c15724e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,4 @@ +# 2017-09-14 clone_depth: 1 version: '{branch}-{build}' configuration: Debug diff --git a/osu-framework b/osu-framework index 1a259925b8..7347c386dc 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 1a259925b82c31ddcebf7b330a6ef9d3a9daf089 +Subproject commit 7347c386dcd10eb799b1ce1512536879328109f9 diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config index 6711f9c54e..2fae7a5e1c 100644 --- a/osu.Desktop.Deploy/App.config +++ b/osu.Desktop.Deploy/App.config @@ -33,7 +33,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - + diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index c090342a4b..a66c9c8993 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -66,22 +66,23 @@ True - $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - - $(SolutionDir)\packages\squirrel.windows.1.7.5\lib\Net45\NuGet.Squirrel.dll + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - $(SolutionDir)\packages\SharpCompress.0.17.1\lib\net45\SharpCompress.dll + + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll + True + + + $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll True - - $(SolutionDir)\packages\squirrel.windows.1.7.5\lib\Net45\Squirrel.dll + + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll True diff --git a/osu.Desktop.Deploy/packages.config b/osu.Desktop.Deploy/packages.config index 3c5ca9f9a3..7725be5f5e 100644 --- a/osu.Desktop.Deploy/packages.config +++ b/osu.Desktop.Deploy/packages.config @@ -6,9 +6,9 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - - - + + + - + \ No newline at end of file diff --git a/osu.Desktop.Tests/OpenTK.dll.config b/osu.Desktop.Tests/OpenTK.dll.config new file mode 100644 index 0000000000..5620e3d9e2 --- /dev/null +++ b/osu.Desktop.Tests/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs new file mode 100644 index 0000000000..878198e8d2 --- /dev/null +++ b/osu.Desktop.Tests/Visual/TestCaseStoryboard.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 OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Storyboards.Drawables; + +namespace osu.Desktop.Tests.Visual +{ + internal class TestCaseStoryboard : OsuTestCase + { + public override string Description => @"Tests storyboards."; + + private readonly Bindable beatmapBacking = new Bindable(); + + private readonly Container storyboardContainer; + private DrawableStoryboard storyboard; + + public TestCaseStoryboard() + { + Clock = new FramedClock(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + storyboardContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + }, + }); + Add(new MusicController + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + State = Visibility.Visible, + }); + + AddStep("Restart", restart); + AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; }); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + beatmapBacking.BindTo(game.Beatmap); + beatmapBacking.ValueChanged += beatmapChanged; + } + + private void beatmapChanged(WorkingBeatmap working) + => loadStoryboard(working); + + private void restart() + { + var track = beatmapBacking.Value.Track; + + track.Reset(); + loadStoryboard(beatmapBacking.Value); + track.Start(); + } + + private void loadStoryboard(WorkingBeatmap working) + { + if (storyboard != null) + storyboardContainer.Remove(storyboard); + + var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; + decoupledClock.ChangeSource(working.Track); + storyboardContainer.Clock = decoupledClock; + + storyboardContainer.Add(storyboard = working.Beatmap.Storyboard.CreateDrawable()); + storyboard.Passing = false; + } + } +} diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index 92ddcc65ca..2811fc2910 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -35,14 +35,15 @@ - $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True - - $(SolutionDir)\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll + + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -91,6 +92,7 @@ + @@ -157,6 +159,7 @@ osu.licenseheader + diff --git a/osu.Desktop.Tests/packages.config b/osu.Desktop.Tests/packages.config index ed487e5cd5..ea33822638 100644 --- a/osu.Desktop.Tests/packages.config +++ b/osu.Desktop.Tests/packages.config @@ -4,9 +4,9 @@ Copyright (c) 2007-2017 ppy Pty Ltd . Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE --> - - - + + + diff --git a/osu.Desktop/OpenTK.dll.config b/osu.Desktop/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Desktop/OpenTK.dll.config +++ b/osu.Desktop/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config new file mode 100644 index 0000000000..a704cc3750 --- /dev/null +++ b/osu.Desktop/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 661c17699b..5eebad47ef 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -136,23 +136,23 @@ - $(SolutionDir)\packages\squirrel.windows.1.7.5\lib\Net45\NuGet.Squirrel.dll + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll True - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True - - $(SolutionDir)\packages\SharpCompress.0.17.1\lib\net45\SharpCompress.dll + + $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll True - - $(SolutionDir)\packages\squirrel.windows.1.7.5\lib\Net45\Squirrel.dll + + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll True @@ -173,6 +173,7 @@ osu.licenseheader + diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 3ad2106d2b..269b901a97 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -7,8 +7,8 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - - + + - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 214cbc7f50..1a9b034cf2 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -9,8 +9,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Scoring; -using osu.Game.Rulesets.Scoring; using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Catch @@ -99,8 +97,6 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap); - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); - public override int LegacyID => 2; public CatchRuleset(RulesetInfo rulesetInfo) diff --git a/osu.Game.Rulesets.Catch/OpenTK.dll.config b/osu.Game.Rulesets.Catch/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Game.Rulesets.Catch/OpenTK.dll.config +++ b/osu.Game.Rulesets.Catch/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 2637a98fe2..11e95622ca 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -18,17 +17,5 @@ namespace osu.Game.Rulesets.Catch.Scoring : base(rulesetContainer) { } - - protected override void Reset() - { - base.Reset(); - - Health.Value = 1; - Accuracy.Value = 1; - } - - protected override void OnNewJudgement(Judgement judgement) - { - } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 0dac3ec41c..2b6f9bbf5a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -45,14 +45,15 @@ namespace osu.Game.Rulesets.Catch.UI public override void Add(DrawableHitObject h) { + h.Depth = (float)h.HitObject.StartTime; + base.Add(h); var fruit = (DrawableFruit)h; fruit.CheckPosition = catcherArea.CheckIfWeCanCatch; - fruit.OnJudgement += Fruit_OnJudgement; } - private void Fruit_OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { if (judgement.IsHit) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 64dbe04ba4..5fc2cf9ef7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Catch.UI float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; - while (Children.OfType().Any(f => Vector2.DistanceSquared(f.Position, fruit.Position) < distance * distance)) + while (Children.OfType().Any(f => Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) { fruit.X += RNG.Next(-5, 5); fruit.Y -= RNG.Next(0, 5); diff --git a/osu.Game.Rulesets.Catch/app.config b/osu.Game.Rulesets.Catch/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game.Rulesets.Catch/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 2ae2262ac7..18e1ee29ca 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -34,7 +34,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -69,6 +69,7 @@ osu.licenseheader + diff --git a/osu.Game.Rulesets.Catch/packages.config b/osu.Game.Rulesets.Catch/packages.config index fa6edb9c8f..0b1838ceee 100644 --- a/osu.Game.Rulesets.Catch/packages.config +++ b/osu.Game.Rulesets.Catch/packages.config @@ -1,9 +1,4 @@  - - - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs index cc7aa63f7e..a8d1b079eb 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs @@ -23,17 +23,5 @@ namespace osu.Game.Rulesets.Mania.Judgements return base.NumericResultFor(HasBroken ? HitResult.Good : result); } } - - protected override int NumericResultForAccuracy(HitResult result) - { - switch (result) - { - default: - return base.NumericResultForAccuracy(result); - case HitResult.Great: - case HitResult.Perfect: - return base.NumericResultForAccuracy(HasBroken ? HitResult.Good : result); - } - } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index c00572edff..d326c6fc0a 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -10,6 +10,5 @@ namespace osu.Game.Rulesets.Mania.Judgements public override bool AffectsCombo => false; protected override int NumericResultFor(HitResult result) => 20; - protected override int NumericResultForAccuracy(HitResult result) => 0; // Don't count ticks into accuracy } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index e36146aa71..1f3b352da4 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -8,11 +8,6 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { - /// - /// The maximum result value for the accuracy portion of the score. - /// - public int MaxNumericAccuracyResult => NumericResultForAccuracy(HitResult.Perfect); - protected override int NumericResultFor(HitResult result) { switch (result) @@ -30,29 +25,5 @@ namespace osu.Game.Rulesets.Mania.Judgements return 300; } } - - public int NumericAccuracyResult => NumericResultForAccuracy(Result); - - /// - /// The result value for the accuracy portion of the score. - /// - protected virtual int NumericResultForAccuracy(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Meh: - return 50; - case HitResult.Ok: - return 100; - case HitResult.Good: - return 200; - case HitResult.Great: - return 300; - case HitResult.Perfect: - return 305; - } - } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0011d2837f..c0996cadf9 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -10,8 +10,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Scoring; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania { @@ -111,8 +109,6 @@ namespace osu.Game.Rulesets.Mania public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); - public override int LegacyID => 3; public ManiaRuleset(RulesetInfo rulesetInfo) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 49bca6d1d7..324f4e4e99 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } - protected ManiaJudgement CreateJudgement() => new HoldNoteTickJudgement(); - protected override void CheckForJudgements(bool userTriggered, double timeOffset) { if (!userTriggered) @@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (HoldStartTime?.Invoke() > HitObject.StartTime) return; - AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); + AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Rulesets.Mania/OpenTK.dll.config b/osu.Game.Rulesets.Mania/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Game.Rulesets.Mania/OpenTK.dll.config +++ b/osu.Game.Rulesets.Mania/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 946caf71cf..a200ba31e2 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.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; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -15,32 +14,6 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - /// - /// The maximum score achievable. - /// Does _not_ include bonus score - for bonus score see . - /// - private const int max_score = 1000000; - - /// - /// The amount of the score attributed to combo. - /// - private const double combo_portion_max = max_score * 0.2; - - /// - /// The amount of the score attributed to accuracy. - /// - private const double accuracy_portion_max = max_score * 0.8; - - /// - /// The factor used to determine relevance of combos. - /// - private const double combo_base = 4; - - /// - /// The combo value at which hit objects result in the max score possible. - /// - private const int combo_relevance_cap = 400; - /// /// The hit HP multiplier at OD = 0. /// @@ -116,42 +89,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - /// - /// The cumulative combo portion of the score. - /// - private double comboScore => combo_portion_max * comboPortion / maxComboPortion; - - /// - /// The cumulative accuracy portion of the score. - /// - private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 4) * totalHits / maxTotalHits; - - /// - /// The cumulative bonus score. - /// This is added on top of , thus the total score can exceed . - /// - private double bonusScore; - - /// - /// The achieved by a perfect playthrough. - /// - private double maxComboPortion; - - /// - /// The portion of the score dedicated to combo. - /// - private double comboPortion; - - /// - /// The achieved by a perfect playthrough. - /// - private int maxTotalHits; - - /// - /// The total hits. - /// - private int totalHits; - public ManiaScoreProcessor() { } @@ -161,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Scoring { } - protected override void ComputeTargets(Beatmap beatmap) + protected override void SimulateAutoplay(Beatmap beatmap) { BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty; hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); @@ -173,11 +110,7 @@ namespace osu.Game.Rulesets.Mania.Scoring { var holdNote = obj as HoldNote; - if (obj is Note) - { - AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); - } - else if (holdNote != null) + if (holdNote != null) { // Head AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); @@ -186,9 +119,9 @@ namespace osu.Game.Rulesets.Mania.Scoring int tickCount = holdNote.Ticks.Count(); for (int i = 0; i < tickCount; i++) AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); - - AddJudgement(new HoldNoteTailJudgement { Result = HitResult.Perfect }); } + + AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); } if (!HasFailed) @@ -197,29 +130,23 @@ namespace osu.Game.Rulesets.Mania.Scoring hpMultiplier *= 1.01; hpMissMultiplier *= 0.98; - Reset(); + Reset(false); } - - maxTotalHits = totalHits; - maxComboPortion = comboPortion; } protected override void OnNewJudgement(Judgement judgement) { + base.OnNewJudgement(judgement); + bool isTick = judgement is HoldNoteTickJudgement; if (isTick) { if (judgement.IsHit) - { Health.Value += hpMultiplier * hp_increase_tick; - bonusScore += judgement.NumericResult; - } } else { - totalHits++; - switch (judgement.Result) { case HitResult.Miss: @@ -241,40 +168,7 @@ namespace osu.Game.Rulesets.Mania.Scoring Health.Value += hpMultiplier * hp_increase_perfect; break; } - - if (judgement.IsHit) - { - // A factor that is applied to make higher combos more relevant - double comboRelevance = Math.Min(Math.Max(0.5, Math.Log(Combo.Value, combo_base)), Math.Log(combo_relevance_cap, combo_base)); - comboPortion += judgement.NumericResult * comboRelevance; - } } - - int scoreForAccuracy = 0; - int maxScoreForAccuracy = 0; - - foreach (var j in Judgements) - { - var maniaJudgement = (ManiaJudgement)j; - - scoreForAccuracy += maniaJudgement.NumericAccuracyResult; - maxScoreForAccuracy += maniaJudgement.MaxNumericAccuracyResult; - } - - Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy; - TotalScore.Value = comboScore + accuracyScore + bonusScore; - } - - protected override void Reset() - { - base.Reset(); - - Health.Value = 1; - Accuracy.Value = 1; - - bonusScore = 0; - comboPortion = 0; - totalHits = 0; } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 1133c9f2ee..2d553f8639 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -203,6 +203,8 @@ namespace osu.Game.Rulesets.Mania.UI /// The DrawableHitObject to add. public override void Add(DrawableHitObject hitObject) { + hitObject.Depth = (float)hitObject.HitObject.StartTime; + hitObject.AccentColour = AccentColour; HitObjects.Add(hitObject); } diff --git a/osu.Game.Rulesets.Mania/app.config b/osu.Game.Rulesets.Mania/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game.Rulesets.Mania/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index bd540f72c0..739f1cf48c 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -34,7 +34,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -110,6 +110,7 @@ osu.licenseheader + diff --git a/osu.Game.Rulesets.Mania/packages.config b/osu.Game.Rulesets.Mania/packages.config index 8add43d5d5..0b1838ceee 100644 --- a/osu.Game.Rulesets.Mania/packages.config +++ b/osu.Game.Rulesets.Mania/packages.config @@ -1,8 +1,4 @@  - - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index fce0188cda..621584ce05 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -1,7 +1,7 @@ // 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.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Objects.Types; @@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps //We are no longer within stacking range of the next object. break; - if (Vector2.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || - stackBaseObject is Slider && Vector2.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) + if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || + stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) { stackBaseIndex = n; @@ -130,14 +130,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps * o <- hitCircle has stack of -1 * o <- hitCircle has stack of -2 */ - if (objectN is Slider && Vector2.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; for (int j = n + 1; j <= i; j++) { //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). OsuHitObject objectJ = beatmap.HitObjects[j]; - if (Vector2.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) objectJ.StackHeight -= offset; } @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps break; } - if (Vector2.Distance(objectN.Position, objectI.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance) { //Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. //NOTE: Sliders with start positions stacking are a special case that is also handled here. @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps //We are no longer within stacking range of the previous object. break; - if (Vector2.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { objectN.StackHeight = objectI.StackHeight + 1; objectI = objectN; diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 3d19d19546..28b6a04376 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -8,12 +8,10 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Judgements { - public class SliderTickJudgement : OsuJudgement - { - } - public class OsuJudgement : Judgement { + public override HitResult MaxResult => HitResult.Great; + /// /// The positional hit offset. /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7cb06df679..c5155d1e10 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Scale = s.Scale, ComboColour = s.ComboColour, Samples = s.Samples, - }), + }) }; components.Add(body); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 0938f78843..53b3427fc4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForJudgements(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - AddJudgement(new SliderTickJudgement { Result = Tracking ? HitResult.Perfect : HitResult.Miss }); + AddJudgement(new OsuJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); } protected override void UpdatePreemptState() diff --git a/osu.Game.Rulesets.Osu/OpenTK.dll.config b/osu.Game.Rulesets.Osu/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Game.Rulesets.Osu/OpenTK.dll.config +++ b/osu.Game.Rulesets.Osu/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index c943518b0b..7650a91d7a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -12,8 +12,6 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; @@ -118,8 +116,6 @@ namespace osu.Game.Rulesets.Osu public override string Description => "osu!"; - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); - public override SettingsSubsection CreateSettings() => new OsuSettings(); public override int LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 59d4492480..d299faaae2 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,9 +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 osu.Framework.Configuration; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -18,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public readonly Bindable Mode = new Bindable(ScoringMode.Exponential); - public OsuScoreProcessor() { } @@ -31,31 +27,33 @@ namespace osu.Game.Rulesets.Osu.Scoring private float hpDrainRate; - private int totalAccurateJudgements; - private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly Dictionary comboResultCounts = new Dictionary(); - private double comboMaxScore; - - protected override void ComputeTargets(Beatmap beatmap) + protected override void SimulateAutoplay(Beatmap beatmap) { hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate; - totalAccurateJudgements = beatmap.HitObjects.Count; - foreach (var unused in beatmap.HitObjects) + foreach (var obj in beatmap.HitObjects) { - // TODO: add support for other object types. + var slider = obj as Slider; + if (slider != null) + { + // Head + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + + // Ticks + foreach (var unused in slider.Ticks) + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + } + AddJudgement(new OsuJudgement { Result = HitResult.Great }); } } - protected override void Reset() + protected override void Reset(bool storeResults) { - base.Reset(); - - Health.Value = 1; - Accuracy.Value = 1; + base.Reset(storeResults); scoreResultCounts.Clear(); comboResultCounts.Clear(); @@ -73,6 +71,8 @@ namespace osu.Game.Rulesets.Osu.Scoring protected override void OnNewJudgement(Judgement judgement) { + base.OnNewJudgement(judgement); + var osuJudgement = (OsuJudgement)judgement; if (judgement.Result != HitResult.None) @@ -103,52 +103,6 @@ namespace osu.Game.Rulesets.Osu.Scoring Health.Value -= hpDrainRate * 0.04; break; } - - calculateScore(); - } - - private void calculateScore() - { - int baseScore = 0; - double comboScore = 0; - - int baseMaxScore = 0; - - foreach (var j in Judgements) - { - baseScore += j.NumericResult; - baseMaxScore += j.MaxNumericResult; - - comboScore += j.NumericResult * (1 + Combo.Value / 10d); - } - - Accuracy.Value = (double)baseScore / baseMaxScore; - - if (comboScore > comboMaxScore) - comboMaxScore = comboScore; - - if (baseScore == 0) - TotalScore.Value = 0; - else - { - // temporary to make scoring feel more like score v1 without being score v1. - float exponentialFactor = Mode.Value == ScoringMode.Exponential ? (float)Judgements.Count / 100 : 1; - - TotalScore.Value = - (int) - ( - exponentialFactor * - 700000 * comboScore / comboMaxScore + - 300000 * Math.Pow(Accuracy.Value, 10) * ((double)Judgements.Count / totalAccurateJudgements) + - 0 /* bonusScore */ - ); - } - } - - public enum ScoringMode - { - Standardised, - Exponential } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 17f4f9f541..1bb4e8493b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Osu.UI public override void Add(DrawableHitObject h) { + h.Depth = (float)h.HitObject.StartTime; + var c = h as IDrawableHitObjectWithProxiedApproach; if (c != null) approachCircles.Add(c.ProxiedLayer.CreateProxy()); diff --git a/osu.Game.Rulesets.Osu/app.config b/osu.Game.Rulesets.Osu/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game.Rulesets.Osu/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 857f47f9b9..0963b0ad1b 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -35,7 +35,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -110,6 +110,7 @@ osu.licenseheader + diff --git a/osu.Game.Rulesets.Osu/packages.config b/osu.Game.Rulesets.Osu/packages.config index fa6edb9c8f..0b1838ceee 100644 --- a/osu.Game.Rulesets.Osu/packages.config +++ b/osu.Game.Rulesets.Osu/packages.config @@ -1,9 +1,4 @@  - - - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 0d61494add..c9daef8c99 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -19,10 +19,5 @@ namespace osu.Game.Rulesets.Taiko.Judgements return 200; } } - - protected override int NumericResultForAccuracy(HitResult result) - { - return 0; - } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 9d6b5ca535..3cd134f3f7 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -8,19 +8,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoJudgement : Judgement { - /// - /// The result value for the accuracy portion of the score. - /// - public int ResultNumericForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(Result); - - /// - /// The maximum result value for the accuracy portion of the score. - /// - public int MaxResultValueForAccuracy => NumericResultForAccuracy(HitResult.Great); + public override HitResult MaxResult => HitResult.Great; /// /// Computes the numeric result value for the combo portion of the score. - /// For the accuracy portion of the score (including accuracy percentage), see . /// /// The result to compute the value for. /// The numeric result value. @@ -36,24 +27,5 @@ namespace osu.Game.Rulesets.Taiko.Judgements return 300; } } - - /// - /// Computes the numeric result value for the accuracy portion of the score. - /// For the combo portion of the score, see . - /// - /// The result to compute the value for. - /// The numeric result value. - protected virtual int NumericResultForAccuracy(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Good: - return 150; - case HitResult.Great: - return 300; - } - } } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs index bb67784ab1..f0b57e5c09 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -7,6 +7,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongHitJudgement : TaikoJudgement { + public override bool AffectsCombo => false; + public TaikoStrongHitJudgement() { base.Result = HitResult.Perfect; diff --git a/osu.Game.Rulesets.Taiko/OpenTK.dll.config b/osu.Game.Rulesets.Taiko/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Game.Rulesets.Taiko/OpenTK.dll.config +++ b/osu.Game.Rulesets.Taiko/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 6b99b23a12..abdda9676f 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.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; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -9,33 +8,11 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; -using OpenTK; namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { - /// - /// The maximum score achievable. - /// Does _not_ include bonus score - for bonus score see . - /// - private const int max_score = 1000000; - - /// - /// The amount of the score attributed to combo. - /// - private const double combo_portion_max = max_score * 0.2; - - /// - /// The amount of the score attributed to accuracy. - /// - private const double accuracy_portion_max = max_score * 0.8; - - /// - /// The factor used to determine relevance of combos. - /// - private const double combo_base = 4; - /// /// The HP awarded by a hit. /// @@ -76,40 +53,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// /// Taiko fails at the end of the map if the player has not half-filled their HP bar. /// - public override bool HasFailed => totalHits == maxTotalHits && Health.Value <= 0.5; - - /// - /// The cumulative combo portion of the score. - /// - private double comboScore => combo_portion_max * comboPortion / maxComboPortion; - - /// - /// The cumulative accuracy portion of the score. - /// - private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 3.6) * totalHits / maxTotalHits; - - /// - /// The cumulative bonus score. - /// This is added on top of , thus the total score can exceed . - /// - private double bonusScore; - - /// - /// The multiple of the original score added to the combo portion of the score - /// for correctly hitting a strong hit object with both keys. - /// - private double strongHitScale; + public override bool HasFailed => Hits == MaxHits && Health.Value <= 0.5; private double hpIncreaseTick; private double hpIncreaseGreat; private double hpIncreaseGood; private double hpIncreaseMiss; - private double maxComboPortion; - private double comboPortion; - private int maxTotalHits; - private int totalHits; - public TaikoScoreProcessor() { } @@ -119,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring { } - protected override void ComputeTargets(Beatmap beatmap) + protected override void SimulateAutoplay(Beatmap beatmap) { double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98)); @@ -128,13 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpIncreaseGood = hpMultiplierNormal * hp_hit_good; hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); - var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong); - - // This is a linear function that awards: - // 10 times bonus points for hitting a strong hit object with both keys with 30 strong hit objects in the map - // 3 times bonus points for hitting a strong hit object with both keys with 120 strong hit objects in the map - strongHitScale = -7d / 90d * MathHelper.Clamp(strongHits.Count, 30, 120) + 111d / 9d; - foreach (var obj in beatmap.HitObjects) { if (obj is Hit) @@ -163,46 +106,14 @@ namespace osu.Game.Rulesets.Taiko.Scoring AddJudgement(new TaikoJudgement { Result = HitResult.Great }); } } - - maxTotalHits = totalHits; - maxComboPortion = comboPortion; } protected override void OnNewJudgement(Judgement judgement) { - bool isStrong = judgement is TaikoStrongHitJudgement; + base.OnNewJudgement(judgement); + bool isTick = judgement is TaikoDrumRollTickJudgement; - // Don't consider ticks and strong hits as a type of hit that counts towards map completion - if (!isTick && !isStrong) - totalHits++; - - // Apply score changes - if (judgement.IsHit) - { - double baseValue = judgement.NumericResult; - - if (isStrong) - { - // Add increased score for the previous judgement by hitting a strong hit object with the second key - var prevJudgement = Judgements[Judgements.Count - 1]; - baseValue = prevJudgement.NumericResult * strongHitScale; - - } - - // Add score to portions - if (judgement is TaikoDrumRollTickJudgement) - bonusScore += baseValue; - else - { - // A relevance factor that needs to be applied to make higher combos more relevant - // Value is capped at 400 combo - double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base))); - - comboPortion += baseValue * comboRelevance; - } - } - // Apply HP changes switch (judgement.Result) { @@ -221,32 +132,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring Health.Value += hpIncreaseGreat; break; } - - int scoreForAccuracy = 0; - int maxScoreForAccuracy = 0; - - foreach (var j in Judgements) - { - var taikoJudgement = (TaikoJudgement)j; - - scoreForAccuracy += taikoJudgement.ResultNumericForAccuracy; - maxScoreForAccuracy += taikoJudgement.MaxResultValueForAccuracy; - } - - Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy; - TotalScore.Value = comboScore + accuracyScore + bonusScore; } - protected override void Reset() + protected override void Reset(bool storeResults) { - base.Reset(); + base.Reset(storeResults); Health.Value = 0; - Accuracy.Value = 1; - - bonusScore = 0; - comboPortion = 0; - totalHits = 0; } } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7b1452766e..ea0f0eae1a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -9,8 +9,6 @@ using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Scoring; using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Taiko @@ -101,8 +99,6 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap); - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); - public override int LegacyID => 1; public TaikoRuleset(RulesetInfo rulesetInfo) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2dd06f7179..d9a216bbfc 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -205,6 +205,8 @@ namespace osu.Game.Rulesets.Taiko.UI public override void Add(DrawableHitObject h) { + h.Depth = (float)h.HitObject.StartTime; + base.Add(h); var barline = h as DrawableBarLine; diff --git a/osu.Game.Rulesets.Taiko/app.config b/osu.Game.Rulesets.Taiko/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game.Rulesets.Taiko/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index a840997214..c8915d233e 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -34,7 +34,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -100,6 +100,7 @@ osu.licenseheader + diff --git a/osu.Game.Rulesets.Taiko/packages.config b/osu.Game.Rulesets.Taiko/packages.config index 8add43d5d5..0b1838ceee 100644 --- a/osu.Game.Rulesets.Taiko/packages.config +++ b/osu.Game.Rulesets.Taiko/packages.config @@ -1,8 +1,4 @@  - - + \ No newline at end of file diff --git a/osu.Game.Tests/OpenTK.dll.config b/osu.Game.Tests/OpenTK.dll.config index 627e9f6009..5620e3d9e2 100644 --- a/osu.Game.Tests/OpenTK.dll.config +++ b/osu.Game.Tests/OpenTK.dll.config @@ -1,7 +1,3 @@ - diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 220b1aac7f..07190bedb0 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -30,12 +30,12 @@ false - - $(SolutionDir)\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll + + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True @@ -53,6 +53,7 @@ osu.licenseheader + diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index 9ad76308d7..af47f642e3 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -4,8 +4,8 @@ Copyright (c) 2007-2017 ppy Pty Ltd . Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE --> - - + + \ No newline at end of file diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 15953fcd82..458c2304f2 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO.Serialization; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps { @@ -40,6 +41,11 @@ namespace osu.Game.Beatmaps /// public double TotalBreakTime => Breaks.Sum(b => b.Duration); + /// + /// The Beatmap's Storyboard. + /// + public Storyboard Storyboard = new Storyboard(); + /// /// Constructs a new beatmap. /// @@ -51,6 +57,7 @@ namespace osu.Game.Beatmaps Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; HitObjects = original?.HitObjects ?? HitObjects; + Storyboard = original?.Storyboard ?? Storyboard; } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index ad0b110e48..d8cd58d939 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Beatmaps @@ -62,11 +61,6 @@ namespace osu.Game.Beatmaps public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => null; - public override ScoreProcessor CreateScoreProcessor() - { - throw new NotImplementedException(); - } - public override string Description => "dummy"; public DummyRuleset(RulesetInfo rulesetInfo) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index b51ea607dd..21fee0f465 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -10,6 +10,10 @@ 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 { @@ -238,42 +242,231 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleEvents(Beatmap beatmap, string line) + private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup) { + var depth = 0; + while (line.StartsWith(" ") || line.StartsWith("_")) + { + ++depth; + line = line.Substring(1); + } + decodeVariables(ref line); string[] split = line.Split(','); - EventType type; - if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); - - // Todo: Implement the rest - switch (type) + if (depth == 0) { - case EventType.Video: - case EventType.Background: - string filename = split[2].Trim('"'); + storyboardSprite = null; - if (type == EventType.Background) - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + EventType type; + if (!Enum.TryParse(split[0], out type)) + throw new InvalidDataException($@"Unknown event type {split[0]}"); - break; - case EventType.Break: - var breakEvent = new BreakPeriod - { - StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), - EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) - }; + switch (type) + { + case EventType.Video: + case EventType.Background: + string filename = split[2].Trim('"'); - if (!breakEvent.HasEffect) - return; + if (type == EventType.Background) + beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; - beatmap.Breaks.Add(breakEvent); - break; + 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) { string[] split = line.Split(','); @@ -414,6 +607,8 @@ namespace osu.Game.Beatmaps.Formats Section section = Section.None; bool hasCustomColours = false; + StoryboardSprite storyboardSprite = null; + CommandTimelineGroup timelineGroup = null; string line; while ((line = stream.ReadLine()) != null) @@ -421,7 +616,7 @@ namespace osu.Game.Beatmaps.Formats if (string.IsNullOrEmpty(line)) continue; - if (line.StartsWith(" ") || line.StartsWith("_") || line.StartsWith("//")) + if (line.StartsWith("//")) continue; if (line.StartsWith(@"osu file format v")) @@ -452,7 +647,7 @@ namespace osu.Game.Beatmaps.Formats handleDifficulty(beatmap, line); break; case Section.Events: - handleEvents(beatmap, line); + handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup); break; case Section.TimingPoints: handleTimingPoints(beatmap, line); @@ -509,5 +704,27 @@ namespace osu.Game.Beatmaps.Formats 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 df8e7f5e3b..bb3122489e 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -100,8 +100,11 @@ namespace osu.Game.Beatmaps public void TransferTo(WorkingBeatmap other) { - if (track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) - other.track = track; + lock (trackLock) + { + if (track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) + other.track = track; + } if (background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) other.background = background; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 08745ce6ba..6312db3ad6 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -228,9 +228,9 @@ namespace osu.Game.Graphics.Backgrounds var size = new Vector2(2 * offset.X, offset.Y); var triangle = new Triangle( - particle.Position * Size * DrawInfo.Matrix, - (particle.Position * Size + offset) * DrawInfo.Matrix, - (particle.Position * Size + new Vector2(-offset.X, offset.Y)) * DrawInfo.Matrix + Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) ); ColourInfo colourInfo = DrawInfo.Colour; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 053ed9c191..36f23d1ae9 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.Cursor // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. - startRotation |= Vector2.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30; + startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30; if (startRotation) { diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 32a37a4910..ccc23e3ff6 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.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 OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -112,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseUp(state, args); } - public string[] FilterTerms => new[] { Text }; + public IEnumerable FilterTerms => new[] { Text }; public bool MatchingFilter { diff --git a/osu.Game/OpenTK.dll.config b/osu.Game/OpenTK.dll.config new file mode 100644 index 0000000000..5620e3d9e2 --- /dev/null +++ b/osu.Game/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Game/Overlays/Chat/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelListItem.cs index 8360e793d8..f4cf806044 100644 --- a/osu.Game/Overlays/Chat/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelListItem.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 OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Chat private Color4 topicColour; private Color4 hoverColour; - public string[] FilterTerms => new[] { channel.Name }; + public IEnumerable FilterTerms => new[] { channel.Name }; public bool MatchingFilter { set diff --git a/osu.Game/Overlays/Chat/ChannelSection.cs b/osu.Game/Overlays/Chat/ChannelSection.cs index 1f046aff2a..5068b415bc 100644 --- a/osu.Game/Overlays/Chat/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/ChannelSection.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Chat public readonly FillFlowContainer ChannelFlow; public IEnumerable FilterableChildren => ChannelFlow.Children; - public string[] FilterTerms => new[] { Header }; + public IEnumerable FilterTerms => new[] { Header }; public bool MatchingFilter { set diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b1deae8272..7c28bdea4d 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -12,8 +12,10 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; +using osu.Framework.MathUtils; using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Graphics; @@ -56,7 +58,7 @@ namespace osu.Game.Overlays private readonly Box chatBackground; private readonly Box tabBackground; - private Bindable chatHeight; + public Bindable ChatHeight { get; internal set; } private readonly Container channelSelectionContainer; private readonly ChannelSelectionOverlay channelSelection; @@ -177,18 +179,11 @@ namespace osu.Game.Overlays if (state == Visibility.Visible) { textbox.HoldFocus = false; - if (1f - chatHeight.Value < channel_selection_min_height) - { - chatContainer.ResizeHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint); - channelSelectionContainer.ResizeHeightTo(channel_selection_min_height, 800, Easing.OutQuint); - channelSelection.Show(); - chatHeight.Value = 1f - channel_selection_min_height; - } + if (1f - ChatHeight.Value < channel_selection_min_height) + transformChatHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint); } else - { textbox.HoldFocus = true; - } }; } @@ -202,7 +197,7 @@ namespace osu.Game.Overlays if (!isDragging) return base.OnDragStart(state); - startDragChatHeight = chatHeight.Value; + startDragChatHeight = ChatHeight.Value; return true; } @@ -212,7 +207,13 @@ namespace osu.Game.Overlays { Trace.Assert(state.Mouse.PositionMouseDown != null); - chatHeight.Value = startDragChatHeight - (state.Mouse.Position.Y - state.Mouse.PositionMouseDown.Value.Y) / Parent.DrawSize.Y; + double targetChatHeight = startDragChatHeight - (state.Mouse.Position.Y - state.Mouse.PositionMouseDown.Value.Y) / Parent.DrawSize.Y; + + // If the channel selection screen is shown, mind its minimum height + if (channelSelection.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) + targetChatHeight = 1f - channel_selection_min_height; + + ChatHeight.Value = targetChatHeight; } return true; @@ -272,14 +273,14 @@ namespace osu.Game.Overlays this.api = api; api.Register(this); - chatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); - chatHeight.ValueChanged += h => + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); + ChatHeight.ValueChanged += h => { chatContainer.Height = (float)h; channelSelectionContainer.Height = 1f - (float)h; tabBackground.FadeTo(h == 1 ? 1 : 0.8f, 200); }; - chatHeight.TriggerChange(); + ChatHeight.TriggerChange(); chatBackground.Colour = colours.ChatBlue; } @@ -501,5 +502,26 @@ namespace osu.Game.Overlays api.Queue(req); } + + private void transformChatHeightTo(double newChatHeight, double duration = 0, Easing easing = Easing.None) + { + this.TransformTo(this.PopulateTransform(new TransformChatHeight(), newChatHeight, duration, easing)); + } + + private class TransformChatHeight : Transform + { + private double valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + public override string TargetMember => "ChatHeight.Value"; + + protected override void Apply(ChatOverlay d, double time) => d.ChatHeight.Value = valueAt(time); + protected override void ReadIntoStartValue(ChatOverlay d) => StartValue = d.ChatHeight.Value; + } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 046e56573f..fb84853a0d 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer buttons; - public string[] FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())).ToArray(); + public IEnumerable FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())).ToArray(); public KeyBindingRow(object action, IEnumerable bindings) { @@ -371,4 +371,4 @@ namespace osu.Game.Overlays.KeyBinding } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 2aaa182685..723b3f4e96 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Music return true; } - public string[] FilterTerms { get; private set; } + public IEnumerable FilterTerms { get; private set; } private bool matching = true; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 360e2ad843..6f1eaded7f 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -229,7 +229,7 @@ namespace osu.Game.Overlays.Music private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { - public string[] FilterTerms => new string[] { }; + public IEnumerable FilterTerms => new string[] { }; public bool MatchingFilter { set diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 77a3449b9c..d6a86fe714 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -414,8 +414,8 @@ namespace osu.Game.Overlays.Profile scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); scoreText.Add(createScoreText("Max Combo")); scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replay Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplayWatched.ToString(@"#,0"))); + scoreText.Add(createScoreText("Replays Watched by Others")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); gradeSS.DisplayCount = user.Statistics.GradesCount.SS; gradeSS.Show(); diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs index 0a32b50809..495a2543d1 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug { RelativeSizeAxes = Axes.X, Text = "Force garbage collection", - Action = () => GC.Collect() + Action = GC.Collect }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 01e32b5a1b..833a5ff966 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { RelativeSizeAxes = Axes.X, Text = "Open osu! folder", - Action = () => storage.OpenInNativeExplorer(), + Action = storage.OpenInNativeExplorer, } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 01e73d0168..b68fd4bc04 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { RelativeSizeAxes = Axes.X, Text = "Key Configuration", - Action = () => keyConfig.ToggleVisibility() + Action = keyConfig.ToggleVisibility }, }; } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index c74f4070e7..f2044f178b 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.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 OpenTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.Settings } } - public string[] FilterTerms => new[] { LabelText }; + public IEnumerable FilterTerms => new[] { LabelText }; public bool MatchingFilter { diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index f091192d27..eb6e398477 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public string[] FilterTerms => new[] { Header }; + public IEnumerable FilterTerms => new[] { Header }; private const int header_size = 26; private const int header_margin = 25; diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 0fbb5b92f7..4164ceee21 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings protected abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public string[] FilterTerms => new[] { Header }; + public IEnumerable FilterTerms => new[] { Header }; public bool MatchingFilter { set diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 29cd3ff384..0ae33272a7 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Judgements /// public double TimeOffset { get; internal set; } + /// + /// Whether the should affect the combo portion of the score. + /// If false, the will be considered for the bonus portion of the score. + /// public virtual bool AffectsCombo => true; /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7cf098c3fa..7a26a53c2a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -55,8 +55,6 @@ namespace osu.Game.Rulesets.Objects.Drawables : base(hitObject) { HitObject = hitObject; - - Depth = (float)hitObject.StartTime; } private ArmedState state; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs index 5ba9c2ff4d..538bb826ad 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } public override bool RemoveWhenNotAlive => false; + protected override bool RequiresChildrenUpdate => true; protected DrawableScrollingHitObject(TObject hitObject) : base(hitObject) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 33621662af..587c2fc659 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets @@ -50,8 +49,6 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap); - public abstract ScoreProcessor CreateScoreProcessor(); - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; public abstract string Description { get; } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2ca733d82a..deb87e92d8 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Configuration; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -15,11 +15,17 @@ namespace osu.Game.Rulesets.Scoring public abstract class ScoreProcessor { /// - /// Invoked when the ScoreProcessor is in a failed state. + /// Invoked when the is in a failed state. + /// This may occur regardless of whether an event is invoked. /// Return true if the fail was permitted. /// public event Func Failed; + /// + /// Invoked when all s have been judged. + /// + public event Action AllJudged; + /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . /// @@ -33,7 +39,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The current accuracy. /// - public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// /// The current health. @@ -50,10 +56,15 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt HighestCombo = new BindableInt(); + /// + /// Whether all s have been processed. + /// + protected virtual bool HasCompleted => false; + /// /// Whether the score is in a failed state. /// - public virtual bool HasFailed => false; + public virtual bool HasFailed => Health.Value == Health.MinValue; /// /// Whether this ScoreProcessor has already triggered the failed state. @@ -63,8 +74,6 @@ namespace osu.Game.Rulesets.Scoring protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - - Reset(); } private ScoreRank rankFrom(double acc) @@ -85,11 +94,12 @@ namespace osu.Game.Rulesets.Scoring /// /// Resets this ScoreProcessor to a default state. /// - protected virtual void Reset() + /// Whether to store the current state of the for future use. + protected virtual void Reset(bool storeResults) { TotalScore.Value = 0; - Accuracy.Value = 0; - Health.Value = 0; + Accuracy.Value = 1; + Health.Value = 1; Combo.Value = 0; HighestCombo.Value = 0; @@ -118,6 +128,9 @@ namespace osu.Game.Rulesets.Scoring protected void NotifyNewJudgement(Judgement judgement) { NewJudgement?.Invoke(judgement); + + if (HasCompleted) + AllJudged?.Invoke(); } /// @@ -135,36 +148,55 @@ namespace osu.Game.Rulesets.Scoring } } - public abstract class ScoreProcessor : ScoreProcessor + public class ScoreProcessor : ScoreProcessor where TObject : HitObject { - /// - /// All judgements held by this ScoreProcessor. - /// - protected readonly List Judgements = new List(); + private const double base_portion = 0.3; + private const double combo_portion = 0.7; + private const double max_score = 1000000; - public override bool HasFailed => Health.Value == Health.MinValue; + public readonly Bindable Mode = new Bindable(); + + protected sealed override bool HasCompleted => Hits == MaxHits; + + protected int MaxHits { get; private set; } + protected int Hits { get; private set; } + + private double maxHighestCombo; + + private double maxBaseScore; + private double rollingMaxBaseScore; + private double baseScore; protected ScoreProcessor() { } - protected ScoreProcessor(RulesetContainer rulesetContainer) + public ScoreProcessor(RulesetContainer rulesetContainer) { - Judgements.Capacity = rulesetContainer.Beatmap.HitObjects.Count; + Debug.Assert(base_portion + combo_portion == 1.0); rulesetContainer.OnJudgement += AddJudgement; - ComputeTargets(rulesetContainer.Beatmap); + SimulateAutoplay(rulesetContainer.Beatmap); + Reset(true); - Reset(); + if (maxBaseScore == 0 || maxHighestCombo == 0) + { + Mode.Value = ScoringMode.Exponential; + Mode.Disabled = true; + } } /// - /// Computes target scoring values for this ScoreProcessor. This is equivalent to performing an auto-play of the score to find the values. + /// Simulates an autoplay of s that will be judged by this + /// by adding s for each in the . + /// + /// This is required for to work, otherwise will be used. + /// /// - /// The Beatmap containing the objects that will be judged by this ScoreProcessor. - protected virtual void ComputeTargets(Beatmap beatmap) { } + /// The containing the s that will be judged by this . + protected virtual void SimulateAutoplay(Beatmap beatmap) { } /// /// Adds a judgement to this ScoreProcessor. @@ -172,45 +204,72 @@ namespace osu.Game.Rulesets.Scoring /// The judgement to add. protected void AddJudgement(Judgement judgement) { - bool exists = Judgements.Contains(judgement); - - if (!exists) - { - if (judgement.AffectsCombo) - { - switch (judgement.Result) - { - case HitResult.None: - break; - case HitResult.Miss: - Combo.Value = 0; - break; - default: - Combo.Value++; - break; - } - } - - Judgements.Add(judgement); - OnNewJudgement(judgement); - - NotifyNewJudgement(judgement); - } + OnNewJudgement(judgement); + NotifyNewJudgement(judgement); UpdateFailed(); } - protected override void Reset() + protected virtual void OnNewJudgement(Judgement judgement) { - base.Reset(); + double bonusScore = 0; - Judgements.Clear(); + if (judgement.AffectsCombo) + { + switch (judgement.Result) + { + case HitResult.None: + break; + case HitResult.Miss: + Combo.Value = 0; + break; + default: + Combo.Value++; + break; + } + + baseScore += judgement.NumericResult; + rollingMaxBaseScore += judgement.MaxNumericResult; + + Hits++; + } + else if (judgement.IsHit) + bonusScore += judgement.NumericResult; + + if (rollingMaxBaseScore != 0) + Accuracy.Value = baseScore / rollingMaxBaseScore; + + switch (Mode.Value) + { + case ScoringMode.Standardised: + TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; + break; + case ScoringMode.Exponential: + TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2); + break; + } } - /// - /// Updates any values that need post-processing. Invoked when a new judgement has occurred. - /// - /// The judgement that triggered this calculation. - protected abstract void OnNewJudgement(Judgement judgement); + protected override void Reset(bool storeResults) + { + if (storeResults) + { + MaxHits = Hits; + maxHighestCombo = HighestCombo; + maxBaseScore = baseScore; + } + + base.Reset(storeResults); + + Hits = 0; + baseScore = 0; + rollingMaxBaseScore = 0; + } + } + + public enum ScoringMode + { + Standardised, + Exponential } } diff --git a/osu.Game/Rulesets/Timing/ScrollingContainer.cs b/osu.Game/Rulesets/Timing/ScrollingContainer.cs index 6e77c49e3d..eac596297a 100644 --- a/osu.Game/Rulesets/Timing/ScrollingContainer.cs +++ b/osu.Game/Rulesets/Timing/ScrollingContainer.cs @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Timing internal Axes ScrollingAxes; public override bool RemoveWhenNotAlive => false; + protected override bool RequiresChildrenUpdate => true; /// /// The control point that defines the speed adjustments for this container. This is set by the . diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs index d3bd7685da..81e3a5c70e 100644 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Timing } public override bool RemoveWhenNotAlive => false; + protected override bool RequiresChildrenUpdate => true; /// /// The that defines the speed adjustments. diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index f7ece531c3..86ab9a0199 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -29,11 +29,6 @@ namespace osu.Game.Rulesets.UI /// public abstract class RulesetContainer : Container { - /// - /// Invoked when all the judgeable HitObjects have been judged. - /// - public event Action OnAllJudged; - /// /// Whether to apply adjustments to the child based on our own size. /// @@ -61,11 +56,6 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } - /// - /// Whether all the HitObjects have been judged. - /// - protected abstract bool AllObjectsJudged { get; } - protected readonly Ruleset Ruleset; /// @@ -77,15 +67,6 @@ namespace osu.Game.Rulesets.UI Ruleset = ruleset; } - /// - /// Checks whether all HitObjects have been judged, and invokes OnAllJudged. - /// - protected void CheckAllJudged() - { - if (AllObjectsJudged) - OnAllJudged?.Invoke(); - } - public abstract ScoreProcessor CreateScoreProcessor(); /// @@ -152,7 +133,7 @@ namespace osu.Game.Rulesets.UI public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; - protected override bool AllObjectsJudged => drawableObjects.All(h => h.AllJudged); + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); /// /// The playfield. @@ -162,8 +143,6 @@ namespace osu.Game.Rulesets.UI protected override Container Content => content; private Container content; - private readonly List> drawableObjects = new List>(); - /// /// Whether to assume the beatmap passed into this is for the current ruleset. /// Creates a hit renderer for a beatmap. @@ -250,8 +229,6 @@ namespace osu.Game.Rulesets.UI /// private void loadObjects() { - drawableObjects.Capacity = Beatmap.HitObjects.Count; - foreach (TObject h in Beatmap.HitObjects) { var drawableObject = GetVisualRepresentation(h); @@ -263,10 +240,8 @@ namespace osu.Game.Rulesets.UI { Playfield.OnJudgement(d, j); OnJudgement?.Invoke(j); - CheckAllJudged(); }; - drawableObjects.Add(drawableObject); Playfield.Add(drawableObject); } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index bc94f88866..4b8942349d 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -201,10 +201,10 @@ namespace osu.Game.Screens.Menu var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); var rectangle = new Quad( - (barPosition - bottomOffset) * DrawInfo.Matrix, - (barPosition - bottomOffset + amplitudeOffset) * DrawInfo.Matrix, - (barPosition + bottomOffset) * DrawInfo.Matrix, - (barPosition + bottomOffset + amplitudeOffset) * DrawInfo.Matrix + Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) ); Texture.DrawQuad( diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2544cc2837..1c82d15f50 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { Push(new Editor()); }, OnSolo = delegate { Push(consumeSongSelect()); }, OnMulti = delegate { Push(new Lobby()); }, - OnExit = delegate { Exit(); }, + OnExit = Exit, } } }, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 615e04d6c2..593abb7d26 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -206,10 +206,8 @@ namespace osu.Game.Screens.Play hudOverlay.ModDisplay.Current.BindTo(working.Mods); - //bind RulesetContainer to ScoreProcessor and ourselves (for a pass situation) - RulesetContainer.OnAllJudged += onCompletion; - - //bind ScoreProcessor to ourselves (for a fail situation) + // Bind ScoreProcessor to ourselves + scoreProcessor.AllJudged += onCompletion; scoreProcessor.Failed += onFail; } diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index 541065e532..38c680902a 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Collections.Generic; +using System.Diagnostics; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; @@ -34,10 +35,12 @@ namespace osu.Game.Screens.Play foreach (var h in objects) { - IHasEndTime end = h as IHasEndTime; + var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + + Debug.Assert(endTime >= h.StartTime); int startRange = (int)((h.StartTime - firstHit) / interval); - int endRange = (int)(((end?.EndTime > 0 ? end.EndTime : h.StartTime) - firstHit) / interval); + int endRange = (int)((endTime - firstHit) / interval); for (int i = startRange; i <= endRange; i++) Values[i]++; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f97c4fe420..84457b77a7 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.CentreRight, SelectionChanged = carouselSelectionChanged, BeatmapsChanged = carouselBeatmapsLoaded, - DeleteRequested = b => promptDelete(b), + DeleteRequested = promptDelete, RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); }, HideDifficultyRequested = b => manager.Hide(b), StartRequested = () => carouselRaisedStart(), diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index 7cd81a924d..3d27552212 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Tournament RelativeSizeAxes = Axes.X, Text = "Reset", - Action = () => reset(false) + Action = () => reset() } } } diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs new file mode 100644 index 0000000000..02b5eb0122 --- /dev/null +++ b/osu.Game/Storyboards/CommandLoop.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.Collections.Generic; + +namespace osu.Game.Storyboards +{ + public class CommandLoop : CommandTimelineGroup + { + public double LoopStartTime; + public int LoopCount; + + public override double StartTime => LoopStartTime; + public override double EndTime => LoopStartTime + CommandsDuration * LoopCount; + + public CommandLoop(double startTime, int loopCount) + { + LoopStartTime = startTime; + LoopCount = loopCount; + } + + public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) + { + for (var loop = 0; loop < LoopCount; loop++) + { + var loopOffset = LoopStartTime + loop * CommandsDuration; + foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset)) + yield return command; + } + } + + public override string ToString() + => $"{LoopStartTime} x{LoopCount}"; + } +} diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs new file mode 100644 index 0000000000..b9bb6629d1 --- /dev/null +++ b/osu.Game/Storyboards/CommandTimeline.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 osu.Framework.Caching; +using osu.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Storyboards +{ + public class CommandTimeline : ICommandTimeline + { + private readonly List commands = new List(); + public IEnumerable Commands => commands.OrderBy(c => c.StartTime); + public bool HasCommands => commands.Count > 0; + + private Cached startTimeBacking; + public double StartTime => startTimeBacking.IsValid ? startTimeBacking : (startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue); + + private Cached endTimeBacking; + public double EndTime => endTimeBacking.IsValid ? endTimeBacking : (endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue); + + public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default(T); + public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default(T); + + public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) + { + if (endTime < startTime) + return; + + commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, }); + + startTimeBacking.Invalidate(); + endTimeBacking.Invalidate(); + } + + public override string ToString() + => $"{commands.Count} command(s)"; + + public class TypedCommand : ICommand + { + public Easing Easing { get; set; } + public double StartTime { get; set; } + public double EndTime { get; set; } + public double Duration => EndTime - StartTime; + + public T StartValue; + public T EndValue; + + public int CompareTo(ICommand other) + { + var result = StartTime.CompareTo(other.StartTime); + if (result != 0) return result; + return EndTime.CompareTo(other.EndTime); + } + + public override string ToString() + => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}"; + } + } + + public interface ICommandTimeline + { + double StartTime { get; } + double EndTime { get; } + bool HasCommands { get; } + } + + public interface ICommand : IComparable + { + Easing Easing { get; set; } + double StartTime { get; set; } + double EndTime { get; set; } + double Duration { get; } + } +} diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs new file mode 100644 index 0000000000..332a6f79cb --- /dev/null +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Storyboards +{ + public delegate CommandTimeline CommandTimelineSelector(CommandTimelineGroup commandTimelineGroup); + + public class CommandTimelineGroup + { + public CommandTimeline X = new CommandTimeline(); + public CommandTimeline Y = new CommandTimeline(); + public CommandTimeline Scale = new CommandTimeline(); + public CommandTimeline Rotation = new CommandTimeline(); + public CommandTimeline Colour = new CommandTimeline(); + public CommandTimeline Alpha = new CommandTimeline(); + public CommandTimeline BlendingMode = new CommandTimeline(); + public CommandTimeline FlipH = new CommandTimeline(); + public CommandTimeline FlipV = new CommandTimeline(); + + public IEnumerable Timelines + { + get + { + yield return X; + yield return Y; + yield return Scale; + yield return Rotation; + yield return Colour; + yield return Alpha; + yield return BlendingMode; + yield return FlipH; + yield return FlipV; + } + } + + public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime); + public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime); + public double CommandsDuration => CommandsEndTime - CommandsStartTime; + + public virtual double StartTime => CommandsStartTime; + public virtual double EndTime => CommandsEndTime; + public double Duration => EndTime - StartTime; + + public bool HasCommands => Timelines.Any(t => t.HasCommands); + + public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) + { + if (offset != 0) + return timelineSelector(this).Commands.Select(command => + new CommandTimeline.TypedCommand + { + Easing = command.Easing, + StartTime = offset + command.StartTime, + EndTime = offset + command.EndTime, + StartValue = command.StartValue, + EndValue = command.EndValue, + }); + + return timelineSelector(this).Commands; + } + } +} diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/CommandTrigger.cs new file mode 100644 index 0000000000..e2731f9c45 --- /dev/null +++ b/osu.Game/Storyboards/CommandTrigger.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Storyboards +{ + public class CommandTrigger : CommandTimelineGroup + { + public string TriggerName; + public double TriggerStartTime; + public double TriggerEndTime; + public int GroupNumber; + + public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber) + { + TriggerName = triggerName; + TriggerStartTime = startTime; + TriggerEndTime = endTime; + GroupNumber = groupNumber; + } + + public override string ToString() + => $"{TriggerName} {TriggerStartTime} -> {TriggerEndTime} ({GroupNumber})"; + } +} diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs new file mode 100644 index 0000000000..f88e5d118f --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -0,0 +1,59 @@ +// 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.Textures; +using osu.Game.IO; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboard : Container + { + public Storyboard Storyboard { get; private set; } + + protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); + public override bool HandleInput => false; + + private bool passing = true; + public bool Passing + { + get { return passing; } + set + { + if (passing == value) return; + passing = value; + updateLayerVisibility(); + } + } + + private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + public DrawableStoryboard(Storyboard storyboard) + { + Storyboard = storyboard; + Size = new Vector2(640, 480); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(FileStore fileStore) + { + dependencies.Cache(new TextureStore(new RawTextureLoaderStore(fileStore.Store), false) { ScaleAdjust = 1, }); + + foreach (var layer in Storyboard.Layers) + Add(layer.CreateDrawable()); + } + + private void updateLayerVisibility() + { + foreach (var layer in Children) + layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; + } + } +} diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs new file mode 100644 index 0000000000..d8b7d05ee9 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -0,0 +1,87 @@ +// 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.Animations; +using osu.Framework.Graphics.Textures; +using System.Linq; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardAnimation : TextureAnimation, IFlippable + { + public StoryboardAnimation Animation { get; private set; } + + protected override bool ShouldBeAlive => Animation.HasCommands && base.ShouldBeAlive; + public override bool RemoveWhenNotAlive => !Animation.HasCommands || base.RemoveWhenNotAlive; + + public bool FlipH { get; set; } + public bool FlipV { get; set; } + + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); + + public override Anchor Origin + { + get + { + var origin = base.Origin; + + if (FlipH) + { + if (origin.HasFlag(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlag(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + if (FlipV) + { + if (origin.HasFlag(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlag(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } + + public override bool IsPresent + => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; + + public DrawableStoryboardAnimation(StoryboardAnimation animation) + { + Animation = animation; + Origin = animation.Origin; + Position = animation.InitialPosition; + Repeat = animation.LoopType == AnimationLoopType.LoopForever; + + if (animation.HasCommands) + { + LifetimeStart = animation.StartTime; + LifetimeEnd = animation.EndTime; + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, TextureStore textureStore) + { + var basePath = Animation.Path.ToLowerInvariant(); + for (var frame = 0; frame < Animation.FrameCount; frame++) + { + var framePath = basePath.Replace(".", frame + "."); + + var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + if (path == null) + continue; + + var texture = textureStore.Get(path); + AddFrame(texture, Animation.FrameDelay); + } + Animation.ApplyTransforms(this); + } + } +} diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs new file mode 100644 index 0000000000..2b5db5b6fa --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardLayer : Container + { + public StoryboardLayer Layer { get; private set; } + public bool Enabled; + + public override bool IsPresent => Enabled && base.IsPresent; + + public DrawableStoryboardLayer(StoryboardLayer layer) + { + Layer = layer; + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Enabled = layer.EnabledWhenPassing; + } + + [BackgroundDependencyLoader] + private void load() + { + foreach (var element in Layer.Elements) + { + var drawable = element.CreateDrawable(); + if (drawable != null) + Add(drawable); + } + } + } +} diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs new file mode 100644 index 0000000000..4b491fa008 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -0,0 +1,80 @@ +// 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.Sprites; +using osu.Framework.Graphics.Textures; +using System.Linq; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardSprite : Sprite, IFlippable + { + public StoryboardSprite Sprite { get; private set; } + + protected override bool ShouldBeAlive => Sprite.HasCommands && base.ShouldBeAlive; + public override bool RemoveWhenNotAlive => !Sprite.HasCommands || base.RemoveWhenNotAlive; + + public bool FlipH { get; set; } + public bool FlipV { get; set; } + + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); + + public override Anchor Origin + { + get + { + var origin = base.Origin; + + if (FlipH) + { + if (origin.HasFlag(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlag(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + if (FlipV) + { + if (origin.HasFlag(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlag(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } + + public override bool IsPresent + => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; + + public DrawableStoryboardSprite(StoryboardSprite sprite) + { + Sprite = sprite; + Origin = sprite.Origin; + Position = sprite.InitialPosition; + + if (sprite.HasCommands) + { + LifetimeStart = sprite.StartTime; + LifetimeEnd = sprite.EndTime; + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, TextureStore textureStore) + { + var spritePath = Sprite.Path.ToLowerInvariant(); + var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + if (path == null) + return; + + Texture = textureStore.Get(path); + Sprite.ApplyTransforms(this); + } + } +} diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs new file mode 100644 index 0000000000..3b21c47b96 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawablesExtensions.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Drawables +{ + public static class DrawablesExtensions + { + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformBlendingMode(this T drawable, BlendingMode newValue, double delay = 0) + where T : Drawable + => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingMode(), newValue, delay)); + } + + public class TransformBlendingMode : Transform + { + private BlendingMode valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(Drawable.Blending); + + protected override void Apply(Drawable d, double time) => d.Blending = valueAt(time); + protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending.Mode; + } +} diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs new file mode 100644 index 0000000000..4d21c9d140 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/IFlippable.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Drawables +{ + public interface IFlippable : ITransformable + { + bool FlipH { get; set; } + bool FlipV { get; set; } + } + + public class TransformFlipH : Transform + { + private bool valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(IFlippable.FlipH); + + protected override void Apply(IFlippable d, double time) => d.FlipH = valueAt(time); + protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipH; + } + + public class TransformFlipV : Transform + { + private bool valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(IFlippable.FlipV); + + protected override void Apply(IFlippable d, double time) => d.FlipV = valueAt(time); + protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipV; + } + + public static class FlippableExtensions + { + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0) + where T : IFlippable + => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); + + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0) + where T : IFlippable + => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); + } +} diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs new file mode 100644 index 0000000000..d5fc86b0f7 --- /dev/null +++ b/osu.Game/Storyboards/IStoryboardElement.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.Framework.Graphics; + +namespace osu.Game.Storyboards +{ + public interface IStoryboardElement + { + string Path { get; } + Drawable CreateDrawable(); + } +} diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs new file mode 100644 index 0000000000..111cdd5d41 --- /dev/null +++ b/osu.Game/Storyboards/Storyboard.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 osu.Game.Storyboards.Drawables; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Storyboards +{ + public class Storyboard + { + private readonly Dictionary layers = new Dictionary(); + public IEnumerable Layers => layers.Values; + + public Storyboard() + { + layers.Add("Background", new StoryboardLayer("Background", 3)); + layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); + layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); + layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); + } + + public StoryboardLayer GetLayer(string name) + { + StoryboardLayer layer; + if (!layers.TryGetValue(name, out layer)) + layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1); + + return layer; + } + + public DrawableStoryboard CreateDrawable() + => new DrawableStoryboard(this); + } +} diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs new file mode 100644 index 0000000000..98936df9e5 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardAnimation.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 OpenTK; +using osu.Framework.Graphics; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Storyboards +{ + public class StoryboardAnimation : StoryboardSprite + { + public int FrameCount; + public double FrameDelay; + public AnimationLoopType LoopType; + + public StoryboardAnimation(string path, Anchor origin, Vector2 initialPosition, int frameCount, double frameDelay, AnimationLoopType loopType) + : base(path, origin, initialPosition) + { + FrameCount = frameCount; + FrameDelay = frameDelay; + LoopType = loopType; + } + + public override Drawable CreateDrawable() + => new DrawableStoryboardAnimation(this); + } + + public enum AnimationLoopType + { + LoopForever, + LoopOnce, + } +} diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs new file mode 100644 index 0000000000..f565b13eb5 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardLayer.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 osu.Game.Storyboards.Drawables; +using System.Collections.Generic; + +namespace osu.Game.Storyboards +{ + public class StoryboardLayer + { + public string Name; + public int Depth; + public bool EnabledWhenPassing = true; + public bool EnabledWhenFailing = true; + + private readonly List elements = new List(); + public IEnumerable Elements => elements; + + public StoryboardLayer(string name, int depth) + { + Name = name; + Depth = depth; + } + + public void Add(IStoryboardElement element) + { + elements.Add(element); + } + + public DrawableStoryboardLayer CreateDrawable() + => new DrawableStoryboardLayer(this) { Depth = Depth, }; + } +} diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs new file mode 100644 index 0000000000..bcf6a4329d --- /dev/null +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Storyboards +{ + public class StoryboardSample : IStoryboardElement + { + public string Path { get; set; } + public double Time; + public float Volume; + + public StoryboardSample(string path, double time, float volume) + { + Path = path; + Time = time; + Volume = volume; + } + + public Drawable CreateDrawable() + => null; + } +} diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs new file mode 100644 index 0000000000..598167d720 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -0,0 +1,113 @@ +// 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.Graphics; +using osu.Game.Storyboards.Drawables; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Storyboards +{ + public class StoryboardSprite : IStoryboardElement + { + private readonly List loops = new List(); + private readonly List triggers = new List(); + + public string Path { get; set; } + public Anchor Origin; + public Vector2 InitialPosition; + + public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); + + public double StartTime => Math.Min( + TimelineGroup.HasCommands ? TimelineGroup.CommandsStartTime : double.MaxValue, + loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Min(l => l.StartTime) : double.MaxValue); + + public double EndTime => Math.Max( + TimelineGroup.HasCommands ? TimelineGroup.CommandsEndTime : double.MinValue, + loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Max(l => l.EndTime) : double.MinValue); + + public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); + + private delegate void DrawablePropertyInitializer(Drawable drawable, T value); + private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); + + public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) + { + Path = path; + Origin = origin; + InitialPosition = initialPosition; + } + + public CommandLoop AddLoop(double startTime, int loopCount) + { + var loop = new CommandLoop(startTime, loopCount); + loops.Add(loop); + return loop; + } + + public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber) + { + var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber); + triggers.Add(trigger); + return trigger; + } + + public virtual Drawable CreateDrawable() + => new DrawableStoryboardSprite(this); + + public void ApplyTransforms(Drawable drawable, IEnumerable> triggeredGroups = null) + { + applyCommands(drawable, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value, (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.BlendingMode, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); + + var flippable = drawable as IFlippable; + if (flippable != null) + { + applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); + applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); + } + } + + private void applyCommands(Drawable drawable, IEnumerable.TypedCommand> commands, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) + where T : struct + { + var initialized = false; + foreach (var command in commands.OrderBy(l => l)) + { + if (!initialized) + { + if (alwaysInitialize || command.StartTime == command.EndTime) + initializeProperty.Invoke(drawable, command.StartValue); + initialized = true; + } + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + transform(drawable, command.StartValue, 0, Easing.None); + transform(drawable, command.EndValue, command.Duration, command.Easing); + } + } + } + + private IEnumerable.TypedCommand> getCommands(CommandTimelineSelector timelineSelector, IEnumerable> triggeredGroups) + { + var commands = TimelineGroup.GetCommands(timelineSelector); + foreach (var loop in loops) + commands = commands.Concat(loop.GetCommands(timelineSelector)); + if (triggeredGroups != null) + foreach (var pair in triggeredGroups) + commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); + return commands; + } + + public override string ToString() + => $"{Path}, {Origin}, {InitialPosition}"; + } +} diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs index 5d518f1780..111c901ca0 100644 --- a/osu.Game/Users/Avatar.cs +++ b/osu.Game/Users/Avatar.cs @@ -26,7 +26,7 @@ namespace osu.Game.Users private void load(TextureStore textures) { Texture texture = null; - if (user?.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); + if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); if (texture == null) texture = textures.Get(@"Online/avatar-guest"); Add(new Sprite diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 05f3d65f30..15b57553a6 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -44,7 +44,7 @@ namespace osu.Game.Users public int MaxCombo; [JsonProperty(@"replays_watched_by_others")] - public int ReplayWatched; + public int ReplaysWatched; [JsonProperty(@"grade_counts")] public Grades GradesCount; diff --git a/osu.Game/app.config b/osu.Game/app.config new file mode 100644 index 0000000000..faeaf001de --- /dev/null +++ b/osu.Game/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b707e2f34d..8da45061bc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,15 +37,16 @@ - $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True - $(SolutionDir)\packages\ppy.OpenTK.3.0\lib\net45\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True - + + $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True - $(SolutionDir)\packages\SharpCompress.0.17.1\lib\net45\SharpCompress.dll $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll @@ -80,6 +81,22 @@ + + + + + + + + + + + + + + + + @@ -568,6 +585,8 @@ osu.licenseheader + + diff --git a/osu.Game/packages.config b/osu.Game/packages.config index 434f9328ea..292ba22c06 100644 --- a/osu.Game/packages.config +++ b/osu.Game/packages.config @@ -5,9 +5,9 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste --> - - - + + + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0b8c196ec8..4011e3991f 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -42,6 +42,7 @@ WARNING WARNING ERROR + HINT HINT WARNING WARNING @@ -56,6 +57,8 @@ WARNING HINT HINT + HINT + HINT HINT WARNING WARNING @@ -617,6 +620,7 @@ 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" /> <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />