From 835b60c7ec00f559a66b60f09eebb25da996dca8 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 27 Jun 2020 16:22:51 +0800 Subject: [PATCH 01/35] Implement ResumeOverlay --- .../Resources/Samples/Gameplay/count.wav | Bin 0 -> 12852 bytes .../UI/DrawableSentakkiRuleset.cs | 3 + .../UI/SentakkiResumeOverlay.cs | 68 ++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav create mode 100644 osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs diff --git a/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav b/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav new file mode 100644 index 0000000000000000000000000000000000000000..2d4ee0227c332ce0ad02eb8a3f8bb3b1c4d28f15 GIT binary patch literal 12852 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 CreateDrawableRepresentatio return null; } + protected override ResumeOverlay CreateResumeOverlay() => new SentakkiResumeOverlay(); + protected override PassThroughInputManager CreateInputManager() => new SentakkiInputManager(Ruleset?.RulesetInfo); } } diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs new file mode 100644 index 000000000..c3343e5e4 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs @@ -0,0 +1,68 @@ +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Skinning; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Sentakki.UI +{ + public class SentakkiResumeOverlay : ResumeOverlay + { + protected override string Message => "Prepare for unforeseen consequences..."; + + private double timePassed = 500; + private Bindable tickCount = new Bindable(3); + + private OsuSpriteText counterText; + private readonly SkinnableSound countSound; + + public SentakkiResumeOverlay() + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fill; + Children = new Drawable[]{ + counterText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "", + Font = OsuFont.Torus.With(size: 50), + Colour = Color4.White, + }, + countSound = new SkinnableSound(new SampleInfo("count")) + }; + tickCount.BindValueChanged(ticks => counterText.Text = ticks.NewValue.ToString()); + } + + protected override void Update() + { + base.Update(); + if (tickCount.Value > 0) + { + timePassed += Clock.ElapsedFrameTime; + if (timePassed > 1000) + { + timePassed %= 1000; + --tickCount.Value; + if (tickCount.Value > 0) + countSound?.Play(); + } + if (tickCount.Value == 0) + Resume(); + } + } + + protected override void PopIn() + { + base.PopIn(); + tickCount.Value = 4; + counterText.Text = ""; + timePassed = 500; + } + } +} \ No newline at end of file From 6ed4ab495513a3be7a2bd8581282c2d02a16d3b0 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 27 Jun 2020 16:47:57 +0800 Subject: [PATCH 02/35] Simplify ResumeOverlay --- .../UI/SentakkiResumeOverlay.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs index c3343e5e4..228dcb1ba 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs @@ -6,6 +6,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play; using osuTK.Graphics; +using System; namespace osu.Game.Rulesets.Sentakki.UI { @@ -13,8 +14,8 @@ public class SentakkiResumeOverlay : ResumeOverlay { protected override string Message => "Prepare for unforeseen consequences..."; - private double timePassed = 500; - private Bindable tickCount = new Bindable(3); + private double timePassed = 3500; + private Bindable tickCount = new Bindable(4); private OsuSpriteText counterText; private readonly SkinnableSound countSound; @@ -36,33 +37,29 @@ public SentakkiResumeOverlay() }, countSound = new SkinnableSound(new SampleInfo("count")) }; - tickCount.BindValueChanged(ticks => counterText.Text = ticks.NewValue.ToString()); + tickCount.BindValueChanged( + ticks => + { + counterText.Text = (ticks.NewValue == 4) ? "" : ticks.NewValue.ToString(); + if (ticks.NewValue % 4 != 0) countSound?.Play(); + if (ticks.NewValue == 0) Resume(); + } + ); } protected override void Update() { base.Update(); - if (tickCount.Value > 0) - { - timePassed += Clock.ElapsedFrameTime; - if (timePassed > 1000) - { - timePassed %= 1000; - --tickCount.Value; - if (tickCount.Value > 0) - countSound?.Play(); - } - if (tickCount.Value == 0) - Resume(); - } + timePassed -= Clock.ElapsedFrameTime; + tickCount.Value = (int)Math.Ceiling(timePassed / 1000); } protected override void PopIn() { base.PopIn(); - tickCount.Value = 4; - counterText.Text = ""; - timePassed = 500; + + // Reset the countdown + timePassed = 3500; } } } \ No newline at end of file From 95286a1a4e432a78829aae20519cbafb0edfd7d5 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 27 Jun 2020 19:27:12 +0800 Subject: [PATCH 03/35] Change ResumeOverlay sample --- .../Resources/Samples/Gameplay/Taka.wav | Bin 0 -> 36252 bytes .../Resources/Samples/Gameplay/count.wav | Bin 12852 -> 0 bytes .../UI/SentakkiResumeOverlay.cs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/Taka.wav delete mode 100644 osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav diff --git a/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/Taka.wav b/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/Taka.wav new file mode 100644 index 0000000000000000000000000000000000000000..e04d318d96216c83e06fd812564057b35a0f84be GIT binary patch literal 36252 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav b/osu.Game.Rulesets.Sentakki/Resources/Samples/Gameplay/count.wav deleted file mode 100644 index 2d4ee0227c332ce0ad02eb8a3f8bb3b1c4d28f15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12852 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 From 641d2a3b76fd7e74d921bbdd53f7ec4bdf97afda Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 27 Jun 2020 19:44:56 +0800 Subject: [PATCH 04/35] Show substitute cursor on ResumeOverlay Players are unable to know where their pointer will be otherwise --- .../UI/SentakkiResumeOverlay.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs index 37d7a3467..5643d7c4a 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs @@ -5,6 +5,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osuTK.Graphics; using System; @@ -20,6 +22,10 @@ public class SentakkiResumeOverlay : ResumeOverlay private OsuSpriteText counterText; private readonly SkinnableSound countSound; + private SentakkiCursorContainer localCursorContainer; + + public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; + public SentakkiResumeOverlay() { Origin = Anchor.Centre; @@ -60,6 +66,26 @@ protected override void PopIn() // Reset the countdown timePassed = 3500; + + GameplayCursor.ActiveCursor.Hide(); + + if (localCursorContainer == null) + { + Add(localCursorContainer = new SentakkiCursorContainer()); + localCursorContainer.MoveTo(GameplayCursor.ActiveCursor.Position); + } + + } + protected override void PopOut() + { + base.PopOut(); + + if (localCursorContainer != null && GameplayCursor?.ActiveCursor != null) + GameplayCursor.ActiveCursor.Position = localCursorContainer.Position; + + localCursorContainer?.Expire(); + localCursorContainer = null; + GameplayCursor?.ActiveCursor?.Show(); } } } \ No newline at end of file From 2bdc3f9673d9416d03bc82e41c81aad4ef193664 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 27 Jun 2020 19:58:30 +0800 Subject: [PATCH 05/35] Add test for ResumeOverlay --- .../UI/TestSceneResumeOverlay.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 osu.Game.Rulesets.Sentakki.Tests/UI/TestSceneResumeOverlay.cs diff --git a/osu.Game.Rulesets.Sentakki.Tests/UI/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Sentakki.Tests/UI/TestSceneResumeOverlay.cs new file mode 100644 index 000000000..dcde3c5a4 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki.Tests/UI/TestSceneResumeOverlay.cs @@ -0,0 +1,31 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Sentakki.UI; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Sentakki.Tests.UI +{ + public class TestSceneResumeOverlay : OsuTestScene + { + public TestSceneResumeOverlay() + { + CursorContainer cursor; + ResumeOverlay resume; + + Children = new Drawable[] + { + cursor = new CursorContainer(), + resume = new SentakkiResumeOverlay + { + GameplayCursor = cursor + } + }; + + AddStep("Show ResumeOverlay", () => resume.Show()); + AddAssert("Is overlay shown?", () => resume.State.Value == Visibility.Visible); + AddUntilStep("Wait for countdown to end", () => resume.State.Value == Visibility.Hidden); + } + } +} \ No newline at end of file From 3b6695c65758a9d1ab6fe12137430b144264044e Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 28 Jun 2020 19:45:47 +0800 Subject: [PATCH 06/35] Change text to something more sensible --- osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs index 5643d7c4a..f82e4bd8a 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Sentakki.UI { public class SentakkiResumeOverlay : ResumeOverlay { - protected override string Message => "Prepare for unforeseen consequences..."; + protected override string Message => "Get ready!"; private double timePassed = 3500; private Bindable tickCount = new Bindable(4); @@ -74,8 +74,8 @@ protected override void PopIn() Add(localCursorContainer = new SentakkiCursorContainer()); localCursorContainer.MoveTo(GameplayCursor.ActiveCursor.Position); } - } + protected override void PopOut() { base.PopOut(); From b82c115e2146dddb7bd7f2f48405dd7ddafc0c24 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 28 Jun 2020 19:46:29 +0800 Subject: [PATCH 07/35] Fix potential of being stuck on the ResumeOverlay if a severe lag occurs --- osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs index f82e4bd8a..f77c61336 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiResumeOverlay.cs @@ -48,7 +48,7 @@ public SentakkiResumeOverlay() { counterText.Text = (ticks.NewValue == 4) ? "" : ticks.NewValue.ToString(); if (ticks.NewValue % 4 != 0) countSound?.Play(); - if (ticks.NewValue == 0) Resume(); + if (ticks.NewValue <= 0) Resume(); } ); } From 2c33fbf4e5cd65b71c5e245a186e6c14ec87ce89 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 29 Jun 2020 14:30:43 +0800 Subject: [PATCH 08/35] Add statistics info to statistics screen --- osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs | 18 ++++++++++++++++++ .../osu.Game.Rulesets.Sentakki.csproj | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index 5b9133cdc..b0dbb2349 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -4,7 +4,9 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Scoring; using osu.Game.Overlays.Settings; +using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Sentakki.Beatmaps; @@ -19,6 +21,7 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; + namespace osu.Game.Rulesets.Sentakki { public class SentakkiRuleset : Ruleset @@ -93,6 +96,21 @@ public override IEnumerable GetDefaultKeyBindings(int variant = 0) = new KeyBinding(InputKey.MouseRight, SentakkiAction.Button2), }; + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + } + }; + public override Drawable CreateIcon() => new Sprite { Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/Icon2"), diff --git a/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj b/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj index 083676a0a..2670d13b0 100644 --- a/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj +++ b/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj @@ -10,6 +10,6 @@ osu.Game.Rulesets.Sentakki - + \ No newline at end of file From 7abcab8d0c559a2cf905d4eda6a0d8cfed536802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:22:49 +0000 Subject: [PATCH 09/35] Bump ppy.osu.Game from 2020.623.1 to 2020.629.0 Bumps [ppy.osu.Game](https://github.com/ppy/osu) from 2020.623.1 to 2020.629.0. - [Release notes](https://github.com/ppy/osu/releases) - [Commits](https://github.com/ppy/osu/compare/2020.623.1...2020.629.0) Signed-off-by: dependabot[bot] --- osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj b/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj index 083676a0a..2670d13b0 100644 --- a/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj +++ b/osu.Game.Rulesets.Sentakki/osu.Game.Rulesets.Sentakki.csproj @@ -10,6 +10,6 @@ osu.Game.Rulesets.Sentakki - + \ No newline at end of file From 59fc9e62ac91bf4388eaf97b856c3a380bcb0e01 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 29 Jun 2020 14:49:17 +0800 Subject: [PATCH 10/35] Prevent HitObjects with IgnoreJudgement from appearing in statistics --- osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index b0dbb2349..068d7c6cf 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -14,12 +14,14 @@ using osu.Game.Rulesets.Sentakki.Mods; using osu.Game.Rulesets.Sentakki.Replays; using osu.Game.Rulesets.Sentakki.Scoring; +using osu.Game.Rulesets.Sentakki.Objects; using osu.Game.Rulesets.Sentakki.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Sentakki From 01b9709391f6f0cc898907735ce3531a31ae310b Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 02:23:57 +0800 Subject: [PATCH 11/35] Create JudgementChart drawable --- .../Statistics/TestSceneJudgementChart.cs | 42 +++++ .../Statistics/JudgementChart.cs | 164 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs create mode 100644 osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs new file mode 100644 index 000000000..c53c8749f --- /dev/null +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Game.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Sentakki.Statistics; +using osu.Game.Rulesets.Sentakki.Objects; +using osu.Game.Graphics; +using osu.Game.Tests.Visual; +using System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Sentakki.Tests.Statistics +{ + [TestFixture] + public class TestSceneJudgementChart : OsuTestScene + { + private List testevents = new List + { + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), + }; + public TestSceneJudgementChart() + { + Add(new JudgementChart(testevents)); + } + } +} diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs new file mode 100644 index 000000000..e33b63163 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Sentakki.Objects; +using osu.Game.Scoring; +using osuTK; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Sentakki.Beatmaps; +using osu.Game.Rulesets.Sentakki.Configuration; +using osu.Game.Rulesets.Sentakki.Mods; +using osu.Game.Rulesets.Sentakki.Replays; +using osu.Game.Rulesets.Sentakki.Scoring; +using osu.Game.Rulesets.Sentakki.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Sentakki.Statistics +{ + public class JudgementChart : CompositeDrawable + { + public JudgementChart(List hitEvents) + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + Size = new Vector2(250, 150); + AddRangeInternal(new Drawable[]{ + new NoteEntry(hitEvents){Position = new Vector2(0, 0)}, + new NoteEntry(hitEvents){Position = new Vector2(0, .2f)}, + new NoteEntry(hitEvents){Position = new Vector2(0, .4f)}, + }); + } + public class NoteEntry : Container where T : SentakkiHitObject, new() + { + private Container progressBox; + private List events; + public NoteEntry(List hitEvents) + { + T tmp = new T(); + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + events = hitEvents.Where(e => e.HitObject.GetType() == tmp.GetType() && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(); + RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.Both; + Size = new Vector2(1, .2f); + Masking = true; + BorderThickness = 2; + BorderColour = Color4.Blue; + CornerRadius = 5; + CornerExponent = 2.5f; + + InternalChildren = new Drawable[]{ + new Box{ + RelativeSizeAxes = Axes.Both, + Colour = Color4.LightBlue, + }, + new Container{ // Left + RelativeSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Size = new Vector2(.33f, 1), + Child = new OsuSpriteText + { + Colour = Color4.Blue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = typeof(T).Name.ToUpper(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) + } + }, + progressBox = new Container{ // Centre + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(.34f, .80f), + Masking = true, + BorderThickness = 2, + BorderColour = Color4.Black, + Children = new Drawable[]{ + new Box{ + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkGray, + } + } + }, + new Container{ // Right + RelativeSizeAxes = Axes.Both, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Size = new Vector2(.33f, 1), + Child = new OsuSpriteText + { + Colour = Color4.Blue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = events.Count.ToString(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) + } + }, + }; + + float MehCount = 0; + float GoodCount = 0; + float GreatCount = 0; + + foreach (var e in events) + { + switch (e.Result) + { + case HitResult.Perfect: + case HitResult.Great: + ++GreatCount; + goto case HitResult.Good; + case HitResult.Good: + ++GoodCount; + goto case HitResult.Meh; + case HitResult.Meh: + ++MehCount; + break; + } + } + + progressBox.AddRange(new Drawable[]{ + new ProgressBox(HitResult.Meh, MehCount/events.Count), + new ProgressBox(HitResult.Good, GoodCount/events.Count), + new ProgressBox(HitResult.Great, GreatCount/events.Count) + }); + } + private class ProgressBox : Container + { + private OsuColour colours = new OsuColour(); + public ProgressBox(HitResult result, float progress) + { + RelativeSizeAxes = Axes.Both; + Size = new Vector2(float.IsNaN(progress) ? 0 : progress, 1); + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.ForHitResult(result) + }); + } + } + } + } +} \ No newline at end of file From 8a2bf79d407628cf6a7407bcf01c43e6f738f1ac Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 02:33:09 +0800 Subject: [PATCH 12/35] Use Judgement chart in statistics screen --- osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs | 13 +++++++++++++ .../Statistics/JudgementChart.cs | 1 + 2 files changed, 14 insertions(+) diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index 068d7c6cf..09f0908e4 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -22,6 +22,8 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; +using osu.Game.Rulesets.Sentakki.Statistics; +using osuTK; namespace osu.Game.Rulesets.Sentakki @@ -108,6 +110,17 @@ public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatma { RelativeSizeAxes = Axes.X, Height = 250 + }) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Judgement Distribution", new JudgementChart(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, 250) }), } } diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index e33b63163..5db07175b 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -47,6 +47,7 @@ public JudgementChart(List hitEvents) new NoteEntry(hitEvents){Position = new Vector2(0, 0)}, new NoteEntry(hitEvents){Position = new Vector2(0, .2f)}, new NoteEntry(hitEvents){Position = new Vector2(0, .4f)}, + new NoteEntry(hitEvents){Position = new Vector2(0, .6f)}, }); } public class NoteEntry : Container where T : SentakkiHitObject, new() From f3d56f653269419d63b2d4cc41524d2e7e341706 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 16:26:18 +0800 Subject: [PATCH 13/35] Select HitEvents before passing them to NoteEntry drawable Using generic classes is not a good idea, especially when considering Breaks --- .../Statistics/JudgementChart.cs | 72 ++++++++----------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 5db07175b..cd8deaa5b 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -1,37 +1,16 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Sentakki.Objects; -using osu.Game.Scoring; using osuTK; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Game.Configuration; -using osu.Game.Overlays.Settings; -using osu.Game.Screens.Ranking.Statistics; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Sentakki.Beatmaps; -using osu.Game.Rulesets.Sentakki.Configuration; -using osu.Game.Rulesets.Sentakki.Mods; -using osu.Game.Rulesets.Sentakki.Replays; -using osu.Game.Rulesets.Sentakki.Scoring; -using osu.Game.Rulesets.Sentakki.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; + using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osuTK.Graphics; namespace osu.Game.Rulesets.Sentakki.Statistics @@ -42,24 +21,32 @@ public JudgementChart(List hitEvents) { Origin = Anchor.Centre; Anchor = Anchor.Centre; - Size = new Vector2(250, 150); + Size = new Vector2(500, 150); AddRangeInternal(new Drawable[]{ - new NoteEntry(hitEvents){Position = new Vector2(0, 0)}, - new NoteEntry(hitEvents){Position = new Vector2(0, .2f)}, - new NoteEntry(hitEvents){Position = new Vector2(0, .4f)}, - new NoteEntry(hitEvents){Position = new Vector2(0, .6f)}, + new NoteEntry("Tap",hitEvents.Where(e=> e.HitObject is Tap && !(e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + Position = new Vector2(0, 0) + }, + new NoteEntry("Hold",hitEvents.Where(e=> e.HitObject is Hold && !(e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + Position = new Vector2(0, .2f) + }, + new NoteEntry("Touch",hitEvents.Where(e=> e.HitObject is Touch).ToList()){ + Position = new Vector2(0, .4f) + }, + new NoteEntry("Touch Hold",hitEvents.Where(e=> e.HitObject is TouchHold).ToList()){ + Position = new Vector2(0, .6f) + }, + new NoteEntry("Break",hitEvents.Where(e=> (e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + Position = new Vector2(0, .8f) + }, }); } - public class NoteEntry : Container where T : SentakkiHitObject, new() + public class NoteEntry : Container { private Container progressBox; - private List events; - public NoteEntry(List hitEvents) + public NoteEntry(string objectName, List hitEvents) { - T tmp = new T(); Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - events = hitEvents.Where(e => e.HitObject.GetType() == tmp.GetType() && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(); RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Both; Size = new Vector2(1, .2f); @@ -81,13 +68,13 @@ public NoteEntry(List hitEvents) Size = new Vector2(.33f, 1), Child = new OsuSpriteText { - Colour = Color4.Blue, + Colour = Color4Extensions.FromHex("#435ca6"), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = typeof(T).Name.ToUpper(), + Text = objectName.ToUpper(), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) } - }, + }, progressBox = new Container{ // Centre RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -101,9 +88,10 @@ public NoteEntry(List hitEvents) RelativeSizeAxes = Axes.Both, Colour = Color4.DarkGray, } - } +} }, - new Container{ // Right + new Container + { // Right RelativeSizeAxes = Axes.Both, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, @@ -113,7 +101,7 @@ public NoteEntry(List hitEvents) Colour = Color4.Blue, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = events.Count.ToString(), + Text = hitEvents.Count.ToString(), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) } }, @@ -123,7 +111,7 @@ public NoteEntry(List hitEvents) float GoodCount = 0; float GreatCount = 0; - foreach (var e in events) + foreach (var e in hitEvents) { switch (e.Result) { @@ -141,9 +129,9 @@ public NoteEntry(List hitEvents) } progressBox.AddRange(new Drawable[]{ - new ProgressBox(HitResult.Meh, MehCount/events.Count), - new ProgressBox(HitResult.Good, GoodCount/events.Count), - new ProgressBox(HitResult.Great, GreatCount/events.Count) + new ProgressBox(HitResult.Meh, MehCount/hitEvents.Count), + new ProgressBox(HitResult.Good, GoodCount/hitEvents.Count), + new ProgressBox(HitResult.Great, GreatCount/hitEvents.Count) }); } private class ProgressBox : Container From 73723a04585c6ef2013c45b3380acac8c030c60a Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 16:32:27 +0800 Subject: [PATCH 14/35] Change colours and text --- .../Statistics/JudgementChart.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index cd8deaa5b..e5ffda004 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -52,14 +52,14 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(1, .2f); Masking = true; BorderThickness = 2; - BorderColour = Color4.Blue; + BorderColour = Color4Extensions.FromHex("#98b8df"); CornerRadius = 5; CornerExponent = 2.5f; InternalChildren = new Drawable[]{ new Box{ RelativeSizeAxes = Axes.Both, - Colour = Color4.LightBlue, + Colour = Color4Extensions.FromHex("#DCE9F9"), }, new Container{ // Left RelativeSizeAxes = Axes.Both, @@ -68,13 +68,13 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(.33f, 1), Child = new OsuSpriteText { - Colour = Color4Extensions.FromHex("#435ca6"), + Colour = Color4Extensions.FromHex("#3c5394"), Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = objectName.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) } - }, + }, progressBox = new Container{ // Centre RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -98,7 +98,7 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(.33f, 1), Child = new OsuSpriteText { - Colour = Color4.Blue, + Colour = Color4Extensions.FromHex("#3c5394"), Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = hitEvents.Count.ToString(), From fb4dbb4a9e08937c1f119fcc1654ac53540eeeac Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 17:18:31 +0800 Subject: [PATCH 15/35] Use different colours on different conditions The box will become gray if no events are found. The box will become Orange if all notes of a particular type is perfectly hit. --- .../Statistics/TestSceneJudgementChart.cs | 1 + .../Statistics/JudgementChart.cs | 108 ++++++++++++------ 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs index c53c8749f..578a75485 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -33,6 +33,7 @@ public class TestSceneJudgementChart : OsuTestScene new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Hold(),new Tap(), null), }; public TestSceneJudgementChart() { diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index e5ffda004..54b442580 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -23,19 +24,34 @@ public JudgementChart(List hitEvents) Anchor = Anchor.Centre; Size = new Vector2(500, 150); AddRangeInternal(new Drawable[]{ - new NoteEntry("Tap",hitEvents.Where(e=> e.HitObject is Tap && !(e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + new NoteEntry + { + ObjectName = "Tap", + HitEvents = hitEvents.Where(e=> e.HitObject is Tap && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), Position = new Vector2(0, 0) }, - new NoteEntry("Hold",hitEvents.Where(e=> e.HitObject is Hold && !(e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + new NoteEntry + { + ObjectName = "Hold", + HitEvents = hitEvents.Where(e=> e.HitObject is Hold && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), Position = new Vector2(0, .2f) }, - new NoteEntry("Touch",hitEvents.Where(e=> e.HitObject is Touch).ToList()){ + new NoteEntry + { + ObjectName = "Touch", + HitEvents = hitEvents.Where(e=> e.HitObject is Touch).ToList(), Position = new Vector2(0, .4f) }, - new NoteEntry("Touch Hold",hitEvents.Where(e=> e.HitObject is TouchHold).ToList()){ + new NoteEntry + { + ObjectName = "Touch Hold", + HitEvents = hitEvents.Where(e=> e.HitObject is TouchHold).ToList(), Position = new Vector2(0, .6f) }, - new NoteEntry("Break",hitEvents.Where(e=> (e.HitObject as SentakkiHitObject).IsBreak).ToList()){ + new NoteEntry + { + ObjectName = "Break", + HitEvents = hitEvents.Where(e=> (e.HitObject as SentakkiHitObject).IsBreak).ToList(), Position = new Vector2(0, .8f) }, }); @@ -43,8 +59,39 @@ public JudgementChart(List hitEvents) public class NoteEntry : Container { private Container progressBox; - public NoteEntry(string objectName, List hitEvents) + + public string ObjectName = "Object"; + public List HitEvents; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { + float MehCount = 0; + float GoodCount = 0; + float GreatCount = 0; + + foreach (var e in HitEvents) + { + switch (e.Result) + { + case HitResult.Perfect: + case HitResult.Great: + ++GreatCount; + goto case HitResult.Good; + case HitResult.Good: + ++GoodCount; + goto case HitResult.Meh; + case HitResult.Meh: + ++MehCount; + break; + } + } + + Color4 textColour = HitEvents.Count == 0 ? Color4Extensions.FromHex("bcbcbc") : (GreatCount == HitEvents.Count) ? Color4.White : Color4Extensions.FromHex("#3c5394"); + Color4 boxColour = HitEvents.Count == 0 ? Color4Extensions.FromHex("808080") : (GreatCount == HitEvents.Count) ? Color4Extensions.FromHex("fda908") : Color4Extensions.FromHex("#DCE9F9"); + Color4 borderColour = HitEvents.Count == 0 ? Color4Extensions.FromHex("536277") : (GreatCount == HitEvents.Count) ? Color4Extensions.FromHex("fda908") : Color4Extensions.FromHex("#98b8df"); + Color4 numberColour = (GreatCount == HitEvents.Count && HitEvents.Count > 0) ? Color4.White : Color4Extensions.FromHex("#3c5394"); + Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; RelativePositionAxes = Axes.Both; @@ -52,14 +99,14 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(1, .2f); Masking = true; BorderThickness = 2; - BorderColour = Color4Extensions.FromHex("#98b8df"); + BorderColour = borderColour; CornerRadius = 5; CornerExponent = 2.5f; InternalChildren = new Drawable[]{ new Box{ RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#DCE9F9"), + Colour = boxColour, }, new Container{ // Left RelativeSizeAxes = Axes.Both, @@ -68,10 +115,10 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(.33f, 1), Child = new OsuSpriteText { - Colour = Color4Extensions.FromHex("#3c5394"), + Colour = textColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = objectName.ToUpper(), + Text = ObjectName.ToUpper(), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) } }, @@ -86,7 +133,7 @@ public NoteEntry(string objectName, List hitEvents) Children = new Drawable[]{ new Box{ RelativeSizeAxes = Axes.Both, - Colour = Color4.DarkGray, + Colour = (HitEvents.Count ==0) ? Color4Extensions.FromHex("343434"):Color4.DarkGray, } } }, @@ -98,49 +145,34 @@ public NoteEntry(string objectName, List hitEvents) Size = new Vector2(.33f, 1), Child = new OsuSpriteText { - Colour = Color4Extensions.FromHex("#3c5394"), + Colour = numberColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = hitEvents.Count.ToString(), + Text = HitEvents.Count.ToString(), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) } }, }; - float MehCount = 0; - float GoodCount = 0; - float GreatCount = 0; - - foreach (var e in hitEvents) - { - switch (e.Result) - { - case HitResult.Perfect: - case HitResult.Great: - ++GreatCount; - goto case HitResult.Good; - case HitResult.Good: - ++GoodCount; - goto case HitResult.Meh; - case HitResult.Meh: - ++MehCount; - break; - } - } - progressBox.AddRange(new Drawable[]{ - new ProgressBox(HitResult.Meh, MehCount/hitEvents.Count), - new ProgressBox(HitResult.Good, GoodCount/hitEvents.Count), - new ProgressBox(HitResult.Great, GreatCount/hitEvents.Count) + new ProgressBox(HitResult.Meh, MehCount/HitEvents.Count), + new ProgressBox(HitResult.Good, GoodCount/HitEvents.Count), + new ProgressBox(HitResult.Great, GreatCount/HitEvents.Count) }); } private class ProgressBox : Container { - private OsuColour colours = new OsuColour(); + private HitResult result; public ProgressBox(HitResult result, float progress) { + this.result = result; RelativeSizeAxes = Axes.Both; Size = new Vector2(float.IsNaN(progress) ? 0 : progress, 1); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { Add(new Box { RelativeSizeAxes = Axes.Both, From 5a57ea26b0f0276050e377679eafe3295edb0472 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 18:01:21 +0800 Subject: [PATCH 16/35] Make sure the count only counts what it should HoldHeads aren't derived from Taps now, so won't be counted as Taps. Hold chart counts each end of the Hold note independently. --- .../Statistics/TestSceneJudgementChart.cs | 3 ++- .../Objects/Drawables/DrawableHold.cs | 2 +- osu.Game.Rulesets.Sentakki/Objects/Hold.cs | 2 +- osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs | 5 +++++ osu.Game.Rulesets.Sentakki/Objects/HoldTail.cs | 7 ------- osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs | 4 +++- 6 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs delete mode 100644 osu.Game.Rulesets.Sentakki/Objects/HoldTail.cs diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs index 578a75485..2e747c6d7 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -33,7 +33,8 @@ public class TestSceneJudgementChart : OsuTestScene new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), - new HitEvent(0,HitResult.Perfect,new Hold(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new HoldHead(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new HoldTail(),new Tap(), null), }; public TestSceneJudgementChart() { diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index 62f006491..ddd340547 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -122,7 +122,7 @@ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) AccentColour = { BindTarget = AccentColour } }; - case Tap _: + case HoldHead _: return new DrawableHoldHead(this) { Anchor = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs index 61c5fcd5f..61f4a0d0c 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs @@ -85,7 +85,7 @@ public override Vector2 EndPosition } } - public readonly Tap Head = new Tap(); + public readonly HoldHead Head = new HoldHead(); public readonly HoldTail Tail = new HoldTail(); diff --git a/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs b/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs new file mode 100644 index 000000000..e97cf3611 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs @@ -0,0 +1,5 @@ +namespace osu.Game.Rulesets.Sentakki.Objects +{ + public class HoldHead : SentakkiHitObject { } + public class HoldTail : SentakkiHitObject { } +} diff --git a/osu.Game.Rulesets.Sentakki/Objects/HoldTail.cs b/osu.Game.Rulesets.Sentakki/Objects/HoldTail.cs deleted file mode 100644 index cb9fea782..000000000 --- a/osu.Game.Rulesets.Sentakki/Objects/HoldTail.cs +++ /dev/null @@ -1,7 +0,0 @@ -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Sentakki.Judgements; - -namespace osu.Game.Rulesets.Sentakki.Objects -{ - public class HoldTail : Tap { } -} diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 54b442580..f9725130a 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -20,6 +20,8 @@ public class JudgementChart : CompositeDrawable { public JudgementChart(List hitEvents) { + + hitEvents = hitEvents.Where(e => !(e.HitObject is Hold)).ToList(); Origin = Anchor.Centre; Anchor = Anchor.Centre; Size = new Vector2(500, 150); @@ -33,7 +35,7 @@ public JudgementChart(List hitEvents) new NoteEntry { ObjectName = "Hold", - HitEvents = hitEvents.Where(e=> e.HitObject is Hold && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), + HitEvents = hitEvents.Where(e=> (e.HitObject is HoldHead || e.HitObject is HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), Position = new Vector2(0, .2f) }, new NoteEntry From 3832de422e332a49f351d62548d9d108432cfbc7 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 18:33:34 +0800 Subject: [PATCH 17/35] Cleanup some stuff --- .../Statistics/TestSceneJudgementChart.cs | 9 ++------- .../Objects/Drawables/DrawableHold.cs | 4 ++-- osu.Game.Rulesets.Sentakki/Objects/Hold.cs | 3 +++ osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs | 5 ----- osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs | 5 +---- .../Statistics/JudgementChart.cs | 10 +++------- 6 files changed, 11 insertions(+), 25 deletions(-) delete mode 100644 osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs index 2e747c6d7..2dbd02e15 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -1,13 +1,8 @@ using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Game.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Sentakki.Statistics; using osu.Game.Rulesets.Sentakki.Objects; -using osu.Game.Graphics; using osu.Game.Tests.Visual; -using System; using System.Collections.Generic; namespace osu.Game.Rulesets.Sentakki.Tests.Statistics @@ -33,8 +28,8 @@ public class TestSceneJudgementChart : OsuTestScene new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), - new HitEvent(0,HitResult.Perfect,new HoldHead(),new Tap(), null), - new HitEvent(0,HitResult.Perfect,new HoldTail(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Hold.HoldHead(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Hold.HoldTail(),new Tap(), null), }; public TestSceneJudgementChart() { diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index ddd340547..f13ecde4c 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -114,7 +114,7 @@ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { - case HoldTail _: + case Hold.HoldTail _: return new DrawableHoldTail(this) { Anchor = Anchor.TopCentre, @@ -122,7 +122,7 @@ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) AccentColour = { BindTarget = AccentColour } }; - case HoldHead _: + case Hold.HoldHead _: return new DrawableHoldHead(this) { Anchor = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs index 61f4a0d0c..3954e680f 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs @@ -100,5 +100,8 @@ protected override void CreateNestedHitObjects() public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public class HoldHead : SentakkiHitObject { } + public class HoldTail : SentakkiHitObject { } } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs b/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs deleted file mode 100644 index e97cf3611..000000000 --- a/osu.Game.Rulesets.Sentakki/Objects/HoldEnds.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace osu.Game.Rulesets.Sentakki.Objects -{ - public class HoldHead : SentakkiHitObject { } - public class HoldTail : SentakkiHitObject { } -} diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index 09f0908e4..523ff50df 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -14,18 +14,15 @@ using osu.Game.Rulesets.Sentakki.Mods; using osu.Game.Rulesets.Sentakki.Replays; using osu.Game.Rulesets.Sentakki.Scoring; -using osu.Game.Rulesets.Sentakki.Objects; +using osu.Game.Rulesets.Sentakki.Statistics; using osu.Game.Rulesets.Sentakki.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Sentakki.Statistics; using osuTK; - namespace osu.Game.Rulesets.Sentakki { public class SentakkiRuleset : Ruleset diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index f9725130a..c7335a625 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions.Color4Extensions; @@ -9,10 +8,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Sentakki.Objects; -using osuTK; - using osu.Game.Rulesets.Scoring; using osuTK.Graphics; +using osuTK; namespace osu.Game.Rulesets.Sentakki.Statistics { @@ -20,8 +18,6 @@ public class JudgementChart : CompositeDrawable { public JudgementChart(List hitEvents) { - - hitEvents = hitEvents.Where(e => !(e.HitObject is Hold)).ToList(); Origin = Anchor.Centre; Anchor = Anchor.Centre; Size = new Vector2(500, 150); @@ -35,7 +31,7 @@ public JudgementChart(List hitEvents) new NoteEntry { ObjectName = "Hold", - HitEvents = hitEvents.Where(e=> (e.HitObject is HoldHead || e.HitObject is HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), + HitEvents = hitEvents.Where(e=> (e.HitObject is Hold.HoldHead || e.HitObject is Hold.HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), Position = new Vector2(0, .2f) }, new NoteEntry @@ -184,4 +180,4 @@ private void load(OsuColour colours) } } } -} \ No newline at end of file +} From 339d1e19a2d18f8c870e778354bf170c19d57042 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 30 Jun 2020 22:17:02 +0800 Subject: [PATCH 18/35] Add Slide entry I know it isn't in the ruleset yet, but doesn't hurt to tease. :) --- .../Statistics/JudgementChart.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index c7335a625..619551909 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -31,26 +31,32 @@ public JudgementChart(List hitEvents) new NoteEntry { ObjectName = "Hold", - HitEvents = hitEvents.Where(e=> (e.HitObject is Hold.HoldHead || e.HitObject is Hold.HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), - Position = new Vector2(0, .2f) + HitEvents = hitEvents.Where(e => (e.HitObject is Hold.HoldHead || e.HitObject is Hold.HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), + Position = new Vector2(0, .16f) + }, + new NoteEntry + { + ObjectName = "Slide", + HitEvents = new List(), + Position = new Vector2(0, .32f) }, new NoteEntry { ObjectName = "Touch", - HitEvents = hitEvents.Where(e=> e.HitObject is Touch).ToList(), - Position = new Vector2(0, .4f) + HitEvents = hitEvents.Where(e => e.HitObject is Touch).ToList(), + Position = new Vector2(0, .48f) }, new NoteEntry { ObjectName = "Touch Hold", - HitEvents = hitEvents.Where(e=> e.HitObject is TouchHold).ToList(), - Position = new Vector2(0, .6f) + HitEvents = hitEvents.Where(e => e.HitObject is TouchHold).ToList(), + Position = new Vector2(0, .64f) }, new NoteEntry { ObjectName = "Break", - HitEvents = hitEvents.Where(e=> (e.HitObject as SentakkiHitObject).IsBreak).ToList(), - Position = new Vector2(0, .8f) + HitEvents = hitEvents.Where(e => (e.HitObject as SentakkiHitObject).IsBreak).ToList(), + Position = new Vector2(0, .80f) }, }); } @@ -94,7 +100,7 @@ private void load(OsuColour colours) Origin = Anchor.TopCentre; RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Both; - Size = new Vector2(1, .2f); + Size = new Vector2(1, .16f); Masking = true; BorderThickness = 2; BorderColour = borderColour; From b5c4b746f3a683aaa337de54b4ad09adb27ae0c5 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 1 Jul 2020 01:55:07 +0800 Subject: [PATCH 19/35] Spice up judgement chart with transforms --- .../Statistics/JudgementChart.cs | 105 ++++++++++++++---- 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 619551909..ba2952565 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Sentakki.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osuTK; @@ -16,53 +17,63 @@ namespace osu.Game.Rulesets.Sentakki.Statistics { public class JudgementChart : CompositeDrawable { + private const double entry_animation_duration = 150; + private const double bar_fill_duration = 3000; public JudgementChart(List hitEvents) { Origin = Anchor.Centre; Anchor = Anchor.Centre; - Size = new Vector2(500, 150); + Size = new Vector2(500, 250); AddRangeInternal(new Drawable[]{ new NoteEntry { ObjectName = "Tap", HitEvents = hitEvents.Where(e=> e.HitObject is Tap && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), - Position = new Vector2(0, 0) + Position = new Vector2(0, 0), + InitialLifetimeOffset = entry_animation_duration * 0 }, new NoteEntry { ObjectName = "Hold", HitEvents = hitEvents.Where(e => (e.HitObject is Hold.HoldHead || e.HitObject is Hold.HoldTail) && !(e.HitObject as SentakkiHitObject).IsBreak).ToList(), - Position = new Vector2(0, .16f) + Position = new Vector2(0, .16f), + InitialLifetimeOffset = entry_animation_duration * 1 }, new NoteEntry { ObjectName = "Slide", HitEvents = new List(), - Position = new Vector2(0, .32f) + Position = new Vector2(0, .32f), + InitialLifetimeOffset = entry_animation_duration * 2 }, new NoteEntry { ObjectName = "Touch", HitEvents = hitEvents.Where(e => e.HitObject is Touch).ToList(), - Position = new Vector2(0, .48f) + Position = new Vector2(0, .48f), + InitialLifetimeOffset = entry_animation_duration * 3 }, new NoteEntry { ObjectName = "Touch Hold", HitEvents = hitEvents.Where(e => e.HitObject is TouchHold).ToList(), - Position = new Vector2(0, .64f) + Position = new Vector2(0, .64f), + InitialLifetimeOffset = entry_animation_duration * 4 }, new NoteEntry { ObjectName = "Break", HitEvents = hitEvents.Where(e => (e.HitObject as SentakkiHitObject).IsBreak).ToList(), - Position = new Vector2(0, .80f) + Position = new Vector2(0, .80f), + InitialLifetimeOffset = entry_animation_duration * 5 }, }); } public class NoteEntry : Container { + public double InitialLifetimeOffset = 0; private Container progressBox; + private RollingCounter noteCounter; public string ObjectName = "Object"; public List HitEvents; @@ -101,18 +112,21 @@ private void load(OsuColour colours) RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Both; Size = new Vector2(1, .16f); + Scale = new Vector2(1, 0); + Alpha = 0; Masking = true; BorderThickness = 2; BorderColour = borderColour; CornerRadius = 5; CornerExponent = 2.5f; + AlwaysPresent = true; InternalChildren = new Drawable[]{ - new Box{ + new Box { RelativeSizeAxes = Axes.Both, Colour = boxColour, }, - new Container{ // Left + new Container { // Left RelativeSizeAxes = Axes.Both, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -126,7 +140,7 @@ private void load(OsuColour colours) Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) } }, - progressBox = new Container{ // Centre + progressBox = new Container { // Centre RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -139,39 +153,80 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both, Colour = (HitEvents.Count ==0) ? Color4Extensions.FromHex("343434"):Color4.DarkGray, } -} + } }, - new Container - { // Right + new Container { // Right RelativeSizeAxes = Axes.Both, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, Size = new Vector2(.33f, 1), - Child = new OsuSpriteText + Child = noteCounter = new TotalNoteCounter { - Colour = numberColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = HitEvents.Count.ToString(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold) + Colour = numberColour, + Current = { Value = 0 }, } }, }; progressBox.AddRange(new Drawable[]{ - new ProgressBox(HitResult.Meh, MehCount/HitEvents.Count), - new ProgressBox(HitResult.Good, GoodCount/HitEvents.Count), - new ProgressBox(HitResult.Great, GreatCount/HitEvents.Count) + new ChartBar(HitResult.Meh, MehCount/HitEvents.Count){ + InitialLifetimeOffset = InitialLifetimeOffset + }, + new ChartBar(HitResult.Good, GoodCount/HitEvents.Count){ + InitialLifetimeOffset = InitialLifetimeOffset + }, + new ChartBar(HitResult.Great, GreatCount/HitEvents.Count){ + InitialLifetimeOffset = InitialLifetimeOffset + }, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScheduleAfterChildren(() => + { + using (BeginDelayedSequence(InitialLifetimeOffset, true)) + { + this.ScaleTo(1, entry_animation_duration, Easing.InOutBack).FadeIn(); + noteCounter.Current.Value = HitEvents.Count; + } }); } - private class ProgressBox : Container + + public class TotalNoteCounter : RollingCounter { + protected override double RollingDuration => bar_fill_duration; + + protected override Easing RollingEasing => Easing.OutPow10; + + public TotalNoteCounter() + { + DisplayedCountSpriteText.Anchor = Anchor.Centre; + DisplayedCountSpriteText.Origin = Anchor.Centre; + DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold); + } + + protected override string FormatCount(long count) => count.ToString("N0"); + + public override void Increment(long amount) + => Current.Value += amount; + } + + private class ChartBar : Container + { + public double InitialLifetimeOffset = 0; + private HitResult result; - public ProgressBox(HitResult result, float progress) + + public ChartBar(HitResult result, float progress) { this.result = result; RelativeSizeAxes = Axes.Both; Size = new Vector2(float.IsNaN(progress) ? 0 : progress, 1); + Scale = new Vector2(0, 1); } [BackgroundDependencyLoader] @@ -183,6 +238,12 @@ private void load(OsuColour colours) Colour = colours.ForHitResult(result) }); } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.Delay(InitialLifetimeOffset).ScaleTo(1, bar_fill_duration, Easing.OutPow10); + } } } } From a04bf730dc8e82d7ded4c5d1370d7c272ebdc583 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 1 Jul 2020 01:56:03 +0800 Subject: [PATCH 20/35] Add extra events in test scene --- .../Statistics/TestSceneJudgementChart.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs index 2dbd02e15..72f1afb05 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -12,6 +12,7 @@ public class TestSceneJudgementChart : OsuTestScene { private List testevents = new List { + // Tap new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Perfect,new Tap(),new Tap(), null), @@ -28,8 +29,29 @@ public class TestSceneJudgementChart : OsuTestScene new HitEvent(0,HitResult.Meh,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), new HitEvent(0,HitResult.Miss,new Tap(),new Tap(), null), + // Holds new HitEvent(0,HitResult.Perfect,new Hold.HoldHead(),new Tap(), null), new HitEvent(0,HitResult.Perfect,new Hold.HoldTail(),new Tap(), null), + // Touch + new HitEvent(0,HitResult.Good,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Good,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Meh,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Miss,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Miss,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Touch(),new Tap(), null), + new HitEvent(0,HitResult.Perfect,new Touch(),new Tap(), null), + // Breaks + new HitEvent(0,HitResult.Perfect,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Good,new Tap(){IsBreak = true},new Tap(), null), + new HitEvent(0,HitResult.Meh,new Tap(){IsBreak = true},new Tap(), null), }; public TestSceneJudgementChart() { From d7977b50a332c30e9c529c17bc11a9e74fab559e Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 1 Jul 2020 15:20:24 +0800 Subject: [PATCH 21/35] Use OutBack easing for card animation --- osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index ba2952565..45bc3a0c2 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -190,7 +190,7 @@ protected override void LoadComplete() { using (BeginDelayedSequence(InitialLifetimeOffset, true)) { - this.ScaleTo(1, entry_animation_duration, Easing.InOutBack).FadeIn(); + this.ScaleTo(1, entry_animation_duration, Easing.OutBack).FadeIn(); noteCounter.Current.Value = HitEvents.Count; } }); From 27e2a052b9a21664800682d995d67c4f6f855c32 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 1 Jul 2020 21:31:28 +0800 Subject: [PATCH 22/35] Make the chart bar corners rounded --- osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 45bc3a0c2..d0bc0e0d6 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -144,7 +144,9 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Size = new Vector2(.34f, .80f), + Size = new Vector2(.34f, .8f), + CornerRadius = 5, + CornerExponent = 2.5f, Masking = true, BorderThickness = 2, BorderColour = Color4.Black, From 7db31e3b97a6cc4bd95ef7d06372057b573935ea Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 5 Jul 2020 00:33:36 +0800 Subject: [PATCH 23/35] Extend Touch hitwindow and improve animation --- .../Objects/Drawables/DrawableTouch.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index 5a4aaf719..3ff13f099 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -103,7 +103,7 @@ private void load(SentakkiRulesetConfigManager sentakkiConfigs) // Easing functions for manual use. private readonly DefaultEasingFunction inOutBack = new DefaultEasingFunction(Easing.InOutBack); - private readonly DefaultEasingFunction inQuint = new DefaultEasingFunction(Easing.InQuint); + private readonly DefaultEasingFunction inOutSine = new DefaultEasingFunction(Easing.InOutSine); protected override void Update() { @@ -111,7 +111,7 @@ protected override void Update() if (Result.HasResult) return; double fadeIn = AnimationDuration.Value * GameplaySpeed; - double moveTo = 500 * GameplaySpeed; + double moveTo = HitObject.HitWindows.WindowFor(HitResult.Meh) * 2 * GameplaySpeed; double animStart = HitObject.StartTime - fadeIn - moveTo; double currentProg = Clock.CurrentTime - animStart; @@ -125,7 +125,7 @@ protected override void Update() float moveAmount = Math.Clamp((float)((currentProg - fadeIn) / moveTo), 0, 1); // Used to simplify this crazy arse manual animating - float moveAnimFormula(float originalValue) => (float)(originalValue - (originalValue * moveAmount * inQuint.ApplyEasing(moveAmount))); + float moveAnimFormula(float originalValue) => (float)(originalValue - (originalValue * inOutSine.ApplyEasing(moveAmount))); blob1.Position = new Vector2(moveAnimFormula(40), 0); blob2.Position = new Vector2(moveAnimFormula(-40), 0); @@ -133,7 +133,7 @@ protected override void Update() blob4.Position = new Vector2(0, moveAnimFormula(-40)); // Used to simplify this crazy arse manual animating - float sizeAnimFormula() => (float)(.5 + .5 * moveAmount * inQuint.ApplyEasing(moveAmount)); + float sizeAnimFormula() => (float)(.5 + .5 * inOutSine.ApplyEasing(moveAmount)); blob1.Scale = new Vector2(sizeAnimFormula()); blob2.Scale = new Vector2(sizeAnimFormula()); @@ -172,6 +172,9 @@ protected override void Update() protected override void CheckForResult(bool userTriggered, double timeOffset) { + // Artificilly extend the hit window + timeOffset /= 2; + Debug.Assert(HitObject.HitWindows != null); if (!userTriggered) From 1412ae3b4536713040f68cda10bc04da02e5c8b0 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 01:19:27 +0800 Subject: [PATCH 24/35] Adjust hit windows to be closer to maimai --- osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs index f745020d4..83738a69b 100644 --- a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs +++ b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs @@ -18,5 +18,12 @@ public override bool IsHitResultAllowed(HitResult result) return false; } } + protected override DifficultyRange[] GetRanges() => new DifficultyRange[]{ + new DifficultyRange(HitResult.Miss, 144,144,144), + new DifficultyRange(HitResult.Meh, 144, 144, 144 ), + new DifficultyRange(HitResult.Good, 96, 96 , 96), + new DifficultyRange(HitResult.Great, 48, 48 , 48), + new DifficultyRange(HitResult.Perfect, 16, 16 ,16) + }; } } From ff0bcdc42b88664fa9a3cfb94f9e8c32287b2110 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 02:40:51 +0800 Subject: [PATCH 25/35] Let TOUCH use its own hit windows --- osu.Game.Rulesets.Sentakki/Objects/Touch.cs | 4 +--- .../Scoring/SentakkiHitWindows.cs | 8 ++++---- .../Scoring/SentakkiTouchHitWindows.cs | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs diff --git a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs index 7272f5bdb..c8677639d 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs @@ -11,8 +11,6 @@ public class Touch : SentakkiHitObject public override float Angle => 0; - // This is not actually used during the result check, since all valid hits result in a perfect judgement - // The only reason that it's here is so that hits show on the accuracy meter at the side. - protected override HitWindows CreateHitWindows() => new SentakkiHitWindows(); + protected override HitWindows CreateHitWindows() => new SentakkiTouchHitWindows(); } } diff --git a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs index 83738a69b..4298fbfc0 100644 --- a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs +++ b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs @@ -19,11 +19,11 @@ public override bool IsHitResultAllowed(HitResult result) } } protected override DifficultyRange[] GetRanges() => new DifficultyRange[]{ - new DifficultyRange(HitResult.Miss, 144,144,144), + new DifficultyRange(HitResult.Miss, 144, 144, 144), new DifficultyRange(HitResult.Meh, 144, 144, 144 ), - new DifficultyRange(HitResult.Good, 96, 96 , 96), - new DifficultyRange(HitResult.Great, 48, 48 , 48), - new DifficultyRange(HitResult.Perfect, 16, 16 ,16) + new DifficultyRange(HitResult.Good, 96, 96, 96), + new DifficultyRange(HitResult.Great, 48, 48, 48), + new DifficultyRange(HitResult.Perfect, 16, 16,16) }; } } diff --git a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs new file mode 100644 index 000000000..0e60896dd --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs @@ -0,0 +1,15 @@ +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Sentakki.Scoring +{ + public class SentakkiTouchHitWindows : SentakkiHitWindows + { + protected override DifficultyRange[] GetRanges() => new DifficultyRange[]{ + new DifficultyRange(HitResult.Miss, 288, 288, 288), + new DifficultyRange(HitResult.Meh, 288, 288, 288 ), + new DifficultyRange(HitResult.Good, 240, 240, 240), + new DifficultyRange(HitResult.Great, 192, 192, 192), + new DifficultyRange(HitResult.Perfect, 144, 144, 144) + }; + } +} From 06627a97023e9f4bb1038052fbeeca8b834854e3 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 17:02:24 +0800 Subject: [PATCH 26/35] Tidy up a bit --- osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs | 5 +++-- .../Scoring/SentakkiTouchHitWindows.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs index 4298fbfc0..aff3ce7dd 100644 --- a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs +++ b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiHitWindows.cs @@ -18,12 +18,13 @@ public override bool IsHitResultAllowed(HitResult result) return false; } } + protected override DifficultyRange[] GetRanges() => new DifficultyRange[]{ new DifficultyRange(HitResult.Miss, 144, 144, 144), - new DifficultyRange(HitResult.Meh, 144, 144, 144 ), + new DifficultyRange(HitResult.Meh, 144, 144, 144), new DifficultyRange(HitResult.Good, 96, 96, 96), new DifficultyRange(HitResult.Great, 48, 48, 48), - new DifficultyRange(HitResult.Perfect, 16, 16,16) + new DifficultyRange(HitResult.Perfect, 16, 16, 16) }; } } diff --git a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs index 0e60896dd..b402815db 100644 --- a/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs +++ b/osu.Game.Rulesets.Sentakki/Scoring/SentakkiTouchHitWindows.cs @@ -6,7 +6,7 @@ public class SentakkiTouchHitWindows : SentakkiHitWindows { protected override DifficultyRange[] GetRanges() => new DifficultyRange[]{ new DifficultyRange(HitResult.Miss, 288, 288, 288), - new DifficultyRange(HitResult.Meh, 288, 288, 288 ), + new DifficultyRange(HitResult.Meh, 288, 288, 288), new DifficultyRange(HitResult.Good, 240, 240, 240), new DifficultyRange(HitResult.Great, 192, 192, 192), new DifficultyRange(HitResult.Perfect, 144, 144, 144) From 53a21176e78614a227d69c7ffb61d112815c6024 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 18:20:56 +0800 Subject: [PATCH 27/35] Remove hitwindow extender Hitwindows for touches have been adjusted in master, so we don't need this --- osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index 3ff13f099..da2c2fc32 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -172,9 +172,6 @@ protected override void Update() protected override void CheckForResult(bool userTriggered, double timeOffset) { - // Artificilly extend the hit window - timeOffset /= 2; - Debug.Assert(HitObject.HitWindows != null); if (!userTriggered) From 8909e22ea15c752abe59c0a1035a8cf9932281fc Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 18:22:23 +0800 Subject: [PATCH 28/35] Don't default to a perfect judgement on early presses We don't want to reward "early"s too much --- osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index da2c2fc32..21b066647 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -187,8 +187,8 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) var result = HitObject.HitWindows.ResultFor(timeOffset); if (timeOffset < 0 && result <= HitResult.Miss) return; - if (result >= HitResult.Meh && timeOffset < 0) - result = HitResult.Perfect; + if (result >= HitResult.Meh && result < HitResult.Great && timeOffset < 0) + result = HitResult.Great; ApplyResult(r => r.Type = result); } From d51269696e67ff44f9de106da3fdea7faf4cc599 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 6 Jul 2020 18:22:35 +0800 Subject: [PATCH 29/35] Change easings again --- .../Objects/Drawables/DrawableTouch.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index 21b066647..d33c8c472 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -102,8 +102,8 @@ private void load(SentakkiRulesetConfigManager sentakkiConfigs) } // Easing functions for manual use. - private readonly DefaultEasingFunction inOutBack = new DefaultEasingFunction(Easing.InOutBack); - private readonly DefaultEasingFunction inOutSine = new DefaultEasingFunction(Easing.InOutSine); + private readonly DefaultEasingFunction outSine = new DefaultEasingFunction(Easing.OutSine); + private readonly DefaultEasingFunction inQuint = new DefaultEasingFunction(Easing.InQuint); protected override void Update() { @@ -118,14 +118,14 @@ protected override void Update() // Calculate initial entry animation float fadeAmount = Math.Clamp((float)(currentProg / fadeIn), 0, 1); - Alpha = fadeAmount * (float)inOutBack.ApplyEasing(fadeAmount); - Scale = new Vector2(fadeAmount * (float)inOutBack.ApplyEasing(fadeAmount)); + Alpha = fadeAmount * (float)outSine.ApplyEasing(fadeAmount); + Scale = new Vector2(fadeAmount * (float)outSine.ApplyEasing(fadeAmount)); // Calculate position float moveAmount = Math.Clamp((float)((currentProg - fadeIn) / moveTo), 0, 1); // Used to simplify this crazy arse manual animating - float moveAnimFormula(float originalValue) => (float)(originalValue - (originalValue * inOutSine.ApplyEasing(moveAmount))); + float moveAnimFormula(float originalValue) => (float)(originalValue - (originalValue * inQuint.ApplyEasing(moveAmount))); blob1.Position = new Vector2(moveAnimFormula(40), 0); blob2.Position = new Vector2(moveAnimFormula(-40), 0); @@ -133,7 +133,7 @@ protected override void Update() blob4.Position = new Vector2(0, moveAnimFormula(-40)); // Used to simplify this crazy arse manual animating - float sizeAnimFormula() => (float)(.5 + .5 * inOutSine.ApplyEasing(moveAmount)); + float sizeAnimFormula() => (float)(.5 + .5 * inQuint.ApplyEasing(moveAmount)); blob1.Scale = new Vector2(sizeAnimFormula()); blob2.Scale = new Vector2(sizeAnimFormula()); From 67dbf1d1dfc3ced96b457d2daddc33330b162905 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 7 Jul 2020 22:46:55 +0800 Subject: [PATCH 30/35] Use pathBindable instead of angle value This will be beneficial in the long term, so we don't have to juggle angles and paths. Also removed position variable from sentakkiHitObject (Touch has the variable by itself) --- .../Objects/TestSceneBreakNote.cs | 3 -- .../Objects/TestSceneHoldNote.cs | 3 -- .../Objects/TestSceneTapNote.cs | 3 -- .../Objects/TestSceneTouchHold.cs | 1 - .../Beatmaps/SentakkiNoteConversions.cs | 30 +++++++------------ .../Beatmaps/SentakkiPatternGenerator.cs | 16 ++++------ .../Objects/Drawables/DrawableHold.cs | 4 +-- .../Drawables/DrawableSentakkiHitObject.cs | 2 +- .../Objects/Drawables/DrawableTap.cs | 22 +++++++------- .../Objects/Drawables/DrawableTouch.cs | 2 +- osu.Game.Rulesets.Sentakki/Objects/Hold.cs | 21 ++++--------- .../Objects/SentakkiHitObject.cs | 15 +++++----- osu.Game.Rulesets.Sentakki/Objects/Touch.cs | 6 ++-- .../UI/SentakkiPlayfield.cs | 6 ++-- 14 files changed, 47 insertions(+), 87 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneBreakNote.cs b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneBreakNote.cs index f0e5902b2..4ab1f740b 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneBreakNote.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneBreakNote.cs @@ -35,9 +35,6 @@ private void testSingle(bool auto = false) { IsBreak = true, StartTime = Time.Current + 1000, - Position = new Vector2(0, -66f), - Angle = 0, - EndPosition = new Vector2(0, -296.5f), }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { }); diff --git a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneHoldNote.cs b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneHoldNote.cs index 7fb6d3d4f..5454241c7 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneHoldNote.cs @@ -45,9 +45,6 @@ private void testSingle(double duration, bool auto = false) { StartTime = Time.Current + 1000, EndTime = Time.Current + 1000 + duration, - Position = new Vector2(0, -66), - EndPosition = new Vector2(0, -296.5f), - Angle = 0f, }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { }); diff --git a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTapNote.cs b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTapNote.cs index ba72248d9..442219038 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTapNote.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTapNote.cs @@ -34,9 +34,6 @@ private void testSingle(bool auto = false) var circle = new Tap { StartTime = Time.Current + 1000, - Position = new Vector2(0, -66f), - Angle = 0, - EndPosition = new Vector2(0, -296.5f), }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { }); diff --git a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTouchHold.cs b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTouchHold.cs index 5f9f54fd3..4b606e088 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTouchHold.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Objects/TestSceneTouchHold.cs @@ -34,7 +34,6 @@ private void testSingle(bool auto = false) { StartTime = Time.Current + 1000, Duration = 5000, - Position = new Vector2(0, 0) }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { }); diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs index 205ed3ba3..2663a71a9 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs @@ -26,11 +26,9 @@ public static List CreateTapNote(HitObject original, int path notes.Add(new Tap { IsBreak = strong, - Angle = path.GetAngleFromPath(), + Path = path, Samples = original.Samples, - StartTime = original.StartTime, - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, path), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, path), + StartTime = original.StartTime }); if (twin && experimental.HasFlag(ConversionExperiments.twins)) { @@ -39,11 +37,9 @@ public static List CreateTapNote(HitObject original, int path notes.Add(new Tap { IsBreak = strong, - Angle = newPath.GetAngleFromPath(), + Path = newPath, Samples = original.Samples, - StartTime = original.StartTime, - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, newPath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, newPath), + StartTime = original.StartTime }); foreach (var note in notes) note.HasTwin = true; @@ -62,7 +58,7 @@ public static List CreateTouchNote(HitObject original, int pa { Samples = original.Samples, StartTime = original.StartTime, - Position = newPos, + Position = newPos }}; return notes; @@ -71,7 +67,7 @@ public static List CreateTouchNote(HitObject original, int pa public static SentakkiHitObject CreateTouchHold(HitObject original) => new TouchHold { - Position = Vector2.Zero, + StartTime = original.StartTime, EndTime = (original as IHasDuration).EndTime, Samples = original.Samples, @@ -88,12 +84,10 @@ public static List CreateHoldNote(HitObject original, int pat notes.Add(new Hold { IsBreak = strong, - Angle = path.GetAngleFromPath(), + Path = path, NodeSamples = curveData.NodeSamples, StartTime = original.StartTime, - EndTime = original.GetEndTime(), - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, path), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, path), + EndTime = original.GetEndTime() }); if (experimental.HasFlag(ConversionExperiments.twins)) @@ -105,12 +99,10 @@ public static List CreateHoldNote(HitObject original, int pat notes.Add(new Hold { IsBreak = strong, - Angle = newPath.GetAngleFromPath(), + Path = newPath, NodeSamples = curveData.NodeSamples, StartTime = original.StartTime, EndTime = original.GetEndTime(), - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, newPath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, newPath), }); foreach (var note in notes) note.HasTwin = true; @@ -160,11 +152,9 @@ public static List CreateTapFromTicks(HitObject original, int case SliderEventType.Repeat: hitObjects.Add(new Tap { - Angle = newPath.GetAngleFromPath(), + Path = newPath, Samples = getTickSamples(original.Samples), StartTime = e.Time, - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, newPath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, newPath), }); break; } diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs index cd84b95da..c07fddb96 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs @@ -124,12 +124,10 @@ private SentakkiHitObject createHoldNote(HitObject original, bool twin = false, return new Hold { IsBreak = isBreak, - Angle = notePath.GetAngleFromPath(), + Path = notePath, NodeSamples = (original as IHasPathWithRepeats).NodeSamples, StartTime = original.StartTime, - EndTime = original.GetEndTime(), - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, notePath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, notePath), + EndTime = original.GetEndTime() }; } private IEnumerable createTapsFromTicks(HitObject original) @@ -164,16 +162,14 @@ private IEnumerable createTapsFromTicks(HitObject original) case SliderEventType.Repeat: yield return new Tap { - Angle = notePath.GetAngleFromPath(), + Path = notePath, Samples = original.Samples.Select(s => new HitSampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume }).ToList(), - StartTime = e.Time, - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, notePath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, notePath), + StartTime = e.Time }; break; } @@ -186,11 +182,9 @@ private SentakkiHitObject createTapNote(HitObject original, bool twin = false, b return new Tap { IsBreak = isBreak, - Angle = notePath.GetAngleFromPath(), + Path = notePath, Samples = original.Samples, StartTime = original.StartTime, - EndPosition = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, notePath), - Position = SentakkiExtensions.GetPathPosition(SentakkiPlayfield.NOTESTARTDISTANCE, notePath), }; } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index f13ecde4c..80a214a03 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -43,12 +43,12 @@ public class DrawableHold : DrawableSentakkiHitObject public DrawableHold(Hold hitObject) : base(hitObject) { + hitObject.PathBindable.BindValueChanged(r => Rotation = r.NewValue.GetAngleFromPath(), true); AccentColour.Value = hitObject.NoteColor; Size = new Vector2(80); Position = Vector2.Zero; Anchor = Anchor.Centre; Origin = Anchor.Centre; - Rotation = HitObject.Angle; AlwaysPresent = true; AddRangeInternal(new Drawable[]{ HitObjectLine = new HitObjectLine(), @@ -82,7 +82,7 @@ public DrawableHold(Hold hitObject) isHitting.Value = false; NoteBody.Glow.FadeOut(100); }, - NoteAngle = HitObject.Angle + NoteAngle = hitObject.PathBindable.Value.GetAngleFromPath() } }); } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs index d0a641c99..584e4cea9 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs @@ -31,7 +31,7 @@ public class DrawableSentakkiHitObject : DrawableHitObject // Used for the animation update protected readonly Bindable AnimationDuration = new Bindable(1000); - protected override float SamplePlaybackPosition => (HitObject.EndPosition.X + SentakkiPlayfield.INTERSECTDISTANCE) / (SentakkiPlayfield.INTERSECTDISTANCE * 2); + protected override float SamplePlaybackPosition => SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Path).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2) + .5f; public SentakkiAction[] HitActions { get; set; } = new[] { SentakkiAction.Button1, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs index 0a03043fe..2546e7d86 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs @@ -5,6 +5,8 @@ using osu.Game.Rulesets.Sentakki.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Sentakki.UI; +using osu.Framework.Utils; using osuTK; using osuTK.Graphics; using System; @@ -22,21 +24,19 @@ public class DrawableTap : DrawableSentakkiHitObject public DrawableTap(SentakkiHitObject hitObject) : base(hitObject) { + + hitObject.PathBindable.BindValueChanged(r => Rotation = r.NewValue.GetAngleFromPath(), true); AccentColour.Value = hitObject.NoteColor; Size = Vector2.Zero; Origin = Anchor.Centre; Anchor = Anchor.Centre; AlwaysPresent = true; AddRangeInternal(new Drawable[] { - HitObjectLine = new HitObjectLine - { - Rotation = HitObject.Angle, - }, + HitObjectLine = new HitObjectLine(), CirclePiece = new TapCircle() { Scale = new Vector2(0f), - Rotation = hitObject.Angle, - Position = HitObject.Position + Position = new Vector2(0, -SentakkiPlayfield.NOTESTARTDISTANCE) }, HitArea = new HitReceptor() { @@ -48,8 +48,8 @@ public DrawableTap(SentakkiHitObject hitObject) UpdateResult(true); return true; }, - Position = hitObject.EndPosition, - NoteAngle = HitObject.Angle + Position = new Vector2(0, -SentakkiPlayfield.INTERSECTDISTANCE), + NoteAngle = HitObject.PathBindable.Value.GetAngleFromPath() }, }); } @@ -79,7 +79,7 @@ protected override void Update() // Calculate position float moveAmount = Math.Clamp((float)((currentProg - animTime) / animTime), 0, 1); - CirclePiece.Position = HitObject.Position + ((HitObject.EndPosition - HitObject.Position) * moveAmount); + CirclePiece.Y = (float)Interpolation.Lerp(-SentakkiPlayfield.NOTESTARTDISTANCE, -SentakkiPlayfield.INTERSECTDISTANCE, moveAmount); // Handle hidden and fadeIn modifications if (IsHidden) @@ -135,12 +135,10 @@ protected override void UpdateStateTransforms(ArmedState state) break; case ArmedState.Miss: - var c = HitObject.Angle + 90; - var d = c * (float)(Math.PI / 180); CirclePiece.ScaleTo(0.5f, time_fade_miss, Easing.InCubic) .FadeColour(Color4.Red, time_fade_miss, Easing.OutQuint) - .MoveToOffset(new Vector2(-(100 * (float)Math.Cos(d)), -(100 * (float)Math.Sin(d))), time_fade_hit, Easing.OutCubic) + .MoveToOffset(new Vector2(0, -100), time_fade_hit, Easing.OutCubic) .FadeOut(time_fade_miss); this.ScaleTo(1f, time_fade_miss).Expire(); diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index 5a4aaf719..f174b402d 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -23,7 +23,7 @@ public class DrawableTouch : DrawableSentakkiHitObject, IDrawableHitObjectWithPr public Drawable ProxiedLayer => this; - protected override float SamplePlaybackPosition => (HitObject.Position.X + SentakkiPlayfield.INTERSECTDISTANCE) / (SentakkiPlayfield.INTERSECTDISTANCE * 2); + protected override float SamplePlaybackPosition => (HitObject as Touch).Position.X / (SentakkiPlayfield.INTERSECTDISTANCE * 2) + .5f; protected override double InitialLifetimeOffset => 6000; diff --git a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs index 3954e680f..60b51a8ba 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs @@ -63,25 +63,14 @@ public override double StartTime } } - public override float Angle + public override int Path { - get => base.Angle; + get => base.Path; set { - base.Angle = value; - Head.Angle = value; - Tail.Angle = value; - } - } - - public override Vector2 EndPosition - { - get => base.EndPosition; - set - { - base.EndPosition = value; - Head.EndPosition = value; - Tail.EndPosition = value; + base.Path = value; + Head.Path = value; + Tail.Path = value; } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs index 1c33274a9..af3cf8c80 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs @@ -1,4 +1,5 @@ -using osu.Game.Rulesets.Judgements; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Sentakki.Judgements; using osu.Game.Rulesets.Sentakki.Scoring; using osu.Game.Rulesets.Scoring; @@ -17,13 +18,13 @@ public abstract class SentakkiHitObject : HitObject public override Judgement CreateJudgement() => IsBreak ? new SentakkiBreakJudgement() : new SentakkiJudgement(); public virtual Color4 NoteColor => IsBreak ? Color4.OrangeRed : (HasTwin ? Color4.Gold : Color4Extensions.FromHex("ff0064")); - public virtual Vector2 EndPosition { get; set; } - public virtual float Angle { get; set; } - public Vector2 Position { get; set; } - - public float X => Position.X; - public float Y => Position.Y; + public readonly BindableInt PathBindable = new BindableInt(0); + public virtual int Path + { + get => PathBindable.Value; + set => PathBindable.Value = value; + } protected override HitWindows CreateHitWindows() => new SentakkiHitWindows(); } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs index c8677639d..b59af4cf1 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs @@ -1,16 +1,16 @@ using osu.Game.Rulesets.Sentakki.Scoring; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; +using osuTK; namespace osu.Game.Rulesets.Sentakki.Objects { public class Touch : SentakkiHitObject { + public Vector2 Position { get; set; } + public override bool IsBreak => false; public override Color4 NoteColor => HasTwin ? Color4.Gold : Color4.Cyan; - - public override float Angle => 0; - protected override HitWindows CreateHitWindows() => new SentakkiTouchHitWindows(); } } diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs index efaa2e3d9..dfd7b3a5c 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs @@ -105,8 +105,6 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) var sentakkiObj = (DrawableSentakkiHitObject)judgedObject; - var b = sentakkiObj.HitObject.Angle + 90; - var a = b *= (float)(Math.PI / 180); DrawableSentakkiJudgement explosion; switch (judgedObject) { @@ -131,8 +129,8 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Position = new Vector2(-(240 * (float)Math.Cos(a)), -(240 * (float)Math.Sin(a))), - Rotation = sentakkiObj.HitObject.Angle, + Position = SentakkiExtensions.GetPathPosition(240, sentakkiObj.HitObject.Path), + Rotation = sentakkiObj.HitObject.Path.GetAngleFromPath(), }; break; } From aa3bb2cdb41271590ad64e5920d3ca91d306f011 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 7 Jul 2020 23:19:25 +0800 Subject: [PATCH 31/35] Use NotePath instead of Angles in HitReceptor --- .../Objects/Drawables/DrawableHold.cs | 8 ++++++-- .../Objects/Drawables/DrawableTap.cs | 8 +++++--- .../Objects/Drawables/Pieces/HitReceptor.cs | 12 +++++++----- osu.Game.Rulesets.Sentakki/SentakkiInputManager.cs | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index 80a214a03..7c18604a0 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -43,7 +43,6 @@ public class DrawableHold : DrawableSentakkiHitObject public DrawableHold(Hold hitObject) : base(hitObject) { - hitObject.PathBindable.BindValueChanged(r => Rotation = r.NewValue.GetAngleFromPath(), true); AccentColour.Value = hitObject.NoteColor; Size = new Vector2(80); Position = Vector2.Zero; @@ -82,9 +81,14 @@ public DrawableHold(Hold hitObject) isHitting.Value = false; NoteBody.Glow.FadeOut(100); }, - NoteAngle = hitObject.PathBindable.Value.GetAngleFromPath() } }); + + hitObject.PathBindable.BindValueChanged(r => + { + Rotation = r.NewValue.GetAngleFromPath(); + HitArea.NotePath = r.NewValue; + }, true); } protected override void AddNestedHitObject(DrawableHitObject hitObject) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs index 2546e7d86..2647ce94c 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs @@ -24,8 +24,6 @@ public class DrawableTap : DrawableSentakkiHitObject public DrawableTap(SentakkiHitObject hitObject) : base(hitObject) { - - hitObject.PathBindable.BindValueChanged(r => Rotation = r.NewValue.GetAngleFromPath(), true); AccentColour.Value = hitObject.NoteColor; Size = Vector2.Zero; Origin = Anchor.Centre; @@ -49,9 +47,13 @@ public DrawableTap(SentakkiHitObject hitObject) return true; }, Position = new Vector2(0, -SentakkiPlayfield.INTERSECTDISTANCE), - NoteAngle = HitObject.PathBindable.Value.GetAngleFromPath() }, }); + hitObject.PathBindable.BindValueChanged(r => + { + Rotation = r.NewValue.GetAngleFromPath(); + HitArea.NotePath = r.NewValue; + }, true); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs index 843ea4148..854671540 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs @@ -21,10 +21,10 @@ public class HitReceptor : CircularContainer, IKeyBindingHandler public Func Hit; public Action Release; - public float NoteAngle = -1; + public int? NotePath = null; public bool HoverAction() { - if (!SentakkiActionInputManager.CurrentAngles.Contains(NoteAngle)) + if (!!NotePath.HasValue || SentakkiActionInputManager.CurrentPath.Contains(NotePath.Value)) { if (SentakkiActionInputManager.PressedActions.Any(action => OnPressed(action))) actions.AddRange(SentakkiActionInputManager.PressedActions.Except(actions)); @@ -44,7 +44,8 @@ public virtual bool OnPressed(SentakkiAction action) { if (IsHovered) { - SentakkiActionInputManager.CurrentAngles.Add(NoteAngle); + if (NotePath.HasValue) + SentakkiActionInputManager.CurrentPath.Add(NotePath.Value); actions.Add(action); return Hit?.Invoke() ?? false; } @@ -57,13 +58,14 @@ public void OnReleased(SentakkiAction action) if (!actions.Any()) { Release?.Invoke(); - SentakkiActionInputManager.CurrentAngles.Remove(NoteAngle); + if (NotePath.HasValue) + SentakkiActionInputManager.CurrentPath.Remove(NotePath.Value); } } protected override void OnHoverLost(HoverLostEvent e) { - SentakkiActionInputManager.CurrentAngles.Remove(NoteAngle); + SentakkiActionInputManager.CurrentPath.Remove(NotePath.Value); if (actions.Any()) { actions.Clear(); diff --git a/osu.Game.Rulesets.Sentakki/SentakkiInputManager.cs b/osu.Game.Rulesets.Sentakki/SentakkiInputManager.cs index 3a9195750..54516e686 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiInputManager.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiInputManager.cs @@ -40,7 +40,7 @@ public SentakkiInputManager(RulesetInfo ruleset) { } - public List CurrentAngles = new List(); + public List CurrentPath = new List(); } public enum SentakkiAction From 2a3f39b16c2b99408374cbdc920ea4ebfdbb705d Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 7 Jul 2020 23:23:46 +0800 Subject: [PATCH 32/35] Fix dumb crashes --- .../Objects/Drawables/DrawableTouch.cs | 2 +- .../Objects/Drawables/Pieces/HitReceptor.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index f174b402d..7f7b8e7e3 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -42,7 +42,7 @@ public class DrawableTouch : DrawableSentakkiHitObject, IDrawableHitObjectWithPr private SentakkiInputManager sentakkiActionInputManager; internal SentakkiInputManager SentakkiActionInputManager => sentakkiActionInputManager ??= GetContainingInputManager() as SentakkiInputManager; - public DrawableTouch(SentakkiHitObject hitObject) : base(hitObject) + public DrawableTouch(Touch hitObject) : base(hitObject) { Size = new Vector2(80); Position = hitObject.Position; diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs index 854671540..5907a94d7 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/Pieces/HitReceptor.cs @@ -24,7 +24,7 @@ public class HitReceptor : CircularContainer, IKeyBindingHandler public int? NotePath = null; public bool HoverAction() { - if (!!NotePath.HasValue || SentakkiActionInputManager.CurrentPath.Contains(NotePath.Value)) + if (!NotePath.HasValue || !SentakkiActionInputManager.CurrentPath.Contains(NotePath.Value)) { if (SentakkiActionInputManager.PressedActions.Any(action => OnPressed(action))) actions.AddRange(SentakkiActionInputManager.PressedActions.Except(actions)); @@ -65,7 +65,8 @@ public void OnReleased(SentakkiAction action) protected override void OnHoverLost(HoverLostEvent e) { - SentakkiActionInputManager.CurrentPath.Remove(NotePath.Value); + if (NotePath.HasValue) + SentakkiActionInputManager.CurrentPath.Remove(NotePath.Value); if (actions.Any()) { actions.Clear(); From 866eaf74a61520051f99dd71b9ba2d064278856a Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 7 Jul 2020 23:55:45 +0800 Subject: [PATCH 33/35] Fix CodeFactor nitpicks --- osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs | 1 - .../Objects/Drawables/DrawableSentakkiHitObject.cs | 2 +- osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs index 2663a71a9..c61089311 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs @@ -67,7 +67,6 @@ public static List CreateTouchNote(HitObject original, int pa public static SentakkiHitObject CreateTouchHold(HitObject original) => new TouchHold { - StartTime = original.StartTime, EndTime = (original as IHasDuration).EndTime, Samples = original.Samples, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs index 584e4cea9..83d71ea05 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs @@ -31,7 +31,7 @@ public class DrawableSentakkiHitObject : DrawableHitObject // Used for the animation update protected readonly Bindable AnimationDuration = new Bindable(1000); - protected override float SamplePlaybackPosition => SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Path).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2) + .5f; + protected override float SamplePlaybackPosition => (SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Path).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + .5f; public SentakkiAction[] HitActions { get; set; } = new[] { SentakkiAction.Button1, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index 7f7b8e7e3..c349d5f8a 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -23,7 +23,7 @@ public class DrawableTouch : DrawableSentakkiHitObject, IDrawableHitObjectWithPr public Drawable ProxiedLayer => this; - protected override float SamplePlaybackPosition => (HitObject as Touch).Position.X / (SentakkiPlayfield.INTERSECTDISTANCE * 2) + .5f; + protected override float SamplePlaybackPosition => ((HitObject as Touch).Position.X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + .5f; protected override double InitialLifetimeOffset => 6000; From 821be42ae1bdde1a2dd4927e6187d3f31237a81b Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 8 Jul 2020 02:19:58 +0800 Subject: [PATCH 34/35] Remove attribution from release changelogs No one ever commits except for me anyways, and having this on seems extremely narcissistic imo... It's as if I want to constantly highlight that this is MY contribution. It just doesn't feel right. --- .grenrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.grenrc b/.grenrc index 38ca32277..ded610533 100644 --- a/.grenrc +++ b/.grenrc @@ -10,7 +10,7 @@ "wontfix" ], "template": { - "issue": "- {{name}} | [{{text}}]({{url}}) by [{{user_login}}]({{user_url}})", + "issue": "- {{name}} | [{{text}}]({{url}})", "group": "\n### {{heading}}\n", "changelogTitle": "" }, From cd3443cb3dfa3dd6de9e01dfff4724b49f41111b Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 8 Jul 2020 19:12:31 +0800 Subject: [PATCH 35/35] Rename path to lane --- .../Beatmaps/SentakkiBeatmapConverter.cs | 8 ++--- .../Beatmaps/SentakkiNoteConversions.cs | 32 +++++++++---------- .../Beatmaps/SentakkiPatternGenerator.cs | 20 ++++++------ .../Objects/Drawables/DrawableHold.cs | 4 +-- .../Drawables/DrawableSentakkiHitObject.cs | 2 +- .../Objects/Drawables/DrawableTap.cs | 4 +-- osu.Game.Rulesets.Sentakki/Objects/Hold.cs | 10 +++--- .../Objects/SentakkiHitObject.cs | 8 ++--- .../SentakkiExtensions.cs | 16 +++++----- .../UI/Components/SentakkiRing.cs | 2 +- .../UI/SentakkiPlayfield.cs | 26 +++++++-------- 11 files changed, 66 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapConverter.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapConverter.cs index a65fd5052..cfe1f50b1 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapConverter.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapConverter.cs @@ -49,7 +49,7 @@ protected override IEnumerable ConvertHitObject(HitObject ori Vector2 newPos = (original as IHasPosition)?.Position ?? Vector2.Zero; newPos.Y = 384 - newPos.Y; - int path = newPos.GetDegreesFromPosition(CENTRE_POINT).GetNotePathFromDegrees(); + int lane = newPos.GetDegreesFromPosition(CENTRE_POINT).GetNoteLaneFromDegrees(); List objects = new List(); if (EnabledExperiments.Value.HasFlag(ConversionExperiments.patternv2)) @@ -65,7 +65,7 @@ protected override IEnumerable ConvertHitObject(HitObject ori switch (original) { case IHasPathWithRepeats _: - objects.AddRange(Conversions.CreateHoldNote(original, path, beatmap, random, EnabledExperiments.Value)); + objects.AddRange(Conversions.CreateHoldNote(original, lane, beatmap, random, EnabledExperiments.Value)); break; case IHasDuration _: @@ -74,9 +74,9 @@ protected override IEnumerable ConvertHitObject(HitObject ori default: if (EnabledExperiments.Value.HasFlag(ConversionExperiments.touch) && (random2.Next() % 10 == 0)) - objects.AddRange(Conversions.CreateTouchNote(original, path, random, EnabledExperiments.Value)); + objects.AddRange(Conversions.CreateTouchNote(original, lane, random, EnabledExperiments.Value)); else - objects.AddRange(Conversions.CreateTapNote(original, path, random, EnabledExperiments.Value)); + objects.AddRange(Conversions.CreateTapNote(original, lane, random, EnabledExperiments.Value)); break; } diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs index c61089311..5d381da03 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiNoteConversions.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Sentakki.Beatmaps { public static class Conversions { - public static List CreateTapNote(HitObject original, int path, Random rng, ConversionExperiments experimental = ConversionExperiments.none) + public static List CreateTapNote(HitObject original, int lane, Random rng, ConversionExperiments experimental = ConversionExperiments.none) { List notes = new List(); bool strong = original.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); @@ -26,18 +26,18 @@ public static List CreateTapNote(HitObject original, int path notes.Add(new Tap { IsBreak = strong, - Path = path, + Lane = lane, Samples = original.Samples, StartTime = original.StartTime }); if (twin && experimental.HasFlag(ConversionExperiments.twins)) { - int newPath = path; - while (path == newPath) newPath = rng.Next(0, 8); + int newPath = lane; + while (lane == newPath) newPath = rng.Next(0, 8); notes.Add(new Tap { IsBreak = strong, - Path = newPath, + Lane = newPath, Samples = original.Samples, StartTime = original.StartTime }); @@ -48,7 +48,7 @@ public static List CreateTapNote(HitObject original, int path return notes; } - public static List CreateTouchNote(HitObject original, int path, Random rng, ConversionExperiments experimental = ConversionExperiments.none) + public static List CreateTouchNote(HitObject original, int lane, Random rng, ConversionExperiments experimental = ConversionExperiments.none) { Vector2 newPos = (original as IHasPosition)?.Position ?? Vector2.Zero; newPos.Y = 384 - newPos.Y - 192; @@ -72,7 +72,7 @@ public static SentakkiHitObject CreateTouchHold(HitObject original) Samples = original.Samples, }; - public static List CreateHoldNote(HitObject original, int path, IBeatmap beatmap, Random rng, ConversionExperiments experimental = ConversionExperiments.none) + public static List CreateHoldNote(HitObject original, int lane, IBeatmap beatmap, Random rng, ConversionExperiments experimental = ConversionExperiments.none) { var curveData = original as IHasPathWithRepeats; @@ -83,7 +83,7 @@ public static List CreateHoldNote(HitObject original, int pat notes.Add(new Hold { IsBreak = strong, - Path = path, + Lane = lane, NodeSamples = curveData.NodeSamples, StartTime = original.StartTime, EndTime = original.GetEndTime() @@ -93,12 +93,12 @@ public static List CreateHoldNote(HitObject original, int pat { if (twin) { - int newPath = path; - while (path == newPath) newPath = rng.Next(0, 8); + int newLane = lane; + while (lane == newLane) newLane = rng.Next(0, 8); notes.Add(new Hold { IsBreak = strong, - Path = newPath, + Lane = newLane, NodeSamples = curveData.NodeSamples, StartTime = original.StartTime, EndTime = original.GetEndTime(), @@ -108,7 +108,7 @@ public static List CreateHoldNote(HitObject original, int pat } else { - var taps = CreateTapFromTicks(original, path, beatmap, rng); + var taps = CreateTapFromTicks(original, lane, beatmap, rng); if (taps.Any()) notes.AddRange(taps); } @@ -117,7 +117,7 @@ public static List CreateHoldNote(HitObject original, int pat return notes; } - public static List CreateTapFromTicks(HitObject original, int path, IBeatmap beatmap, Random rng) + public static List CreateTapFromTicks(HitObject original, int lane, IBeatmap beatmap, Random rng) { var curve = original as IHasPathWithRepeats; double spanDuration = curve.Duration / (curve.RepeatCount + 1); @@ -142,8 +142,8 @@ public static List CreateTapFromTicks(HitObject original, int foreach (var e in SliderEventGenerator.Generate(original.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset, CancellationToken.None)) { - int newPath = path; - while (newPath == path) newPath = rng.Next(0, 8); + int newLane = lane; + while (newLane == lane) newLane = rng.Next(0, 8); switch (e.Type) { @@ -151,7 +151,7 @@ public static List CreateTapFromTicks(HitObject original, int case SliderEventType.Repeat: hitObjects.Add(new Tap { - Path = newPath, + Lane = newLane, Samples = getTickSamples(original.Samples), StartTime = e.Time, }); diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs index c07fddb96..1e69fa48e 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiPatternGenerator.cs @@ -30,17 +30,17 @@ public SentakkiPatternGenerator(IBeatmap beatmap) rng = new Random(seed); } - //The patterns will generate the note path to be used based on the current offset + //The patterns will generate the note lane to be used based on the current offset // argument list is (offset, diff) private List> patternlist => new List>{ - //Stream pattern, path difference determined by offset2 + //Stream pattern, lane difference determined by offset2 (twin)=> { if(twin) return offset + 4; else offset+=offset2; return offset; }, // Back and forth, works better with longer combos - // Path difference determined by offset2, but will make sure offset2 is never 0. + // Lane difference determined by offset2, but will make sure offset2 is never 0. (twin)=>{ offset2 = offset2 == 0 ? 1 : offset2; offset+=offset2; @@ -53,7 +53,7 @@ public SentakkiPatternGenerator(IBeatmap beatmap) private int offset = 0; private int offset2 = 0; - private int getNewPath(bool twin = false) => patternlist[currentPattern].Invoke(twin); + private int getNewLane(bool twin = false) => patternlist[currentPattern].Invoke(twin); public void CreateNewPattern() { @@ -120,11 +120,11 @@ public IEnumerable GenerateNewNote(HitObject original) // Individual note generation code, because it's cleaner private SentakkiHitObject createHoldNote(HitObject original, bool twin = false, bool isBreak = false) { - int notePath = getNewPath(twin); + int noteLane = getNewLane(twin); return new Hold { IsBreak = isBreak, - Path = notePath, + Lane = noteLane, NodeSamples = (original as IHasPathWithRepeats).NodeSamples, StartTime = original.StartTime, EndTime = original.GetEndTime() @@ -132,7 +132,7 @@ private SentakkiHitObject createHoldNote(HitObject original, bool twin = false, } private IEnumerable createTapsFromTicks(HitObject original) { - int notePath = getNewPath(true); + int noteLane = getNewLane(true); var curve = original as IHasPathWithRepeats; double spanDuration = curve.Duration / (curve.RepeatCount + 1); @@ -162,7 +162,7 @@ private IEnumerable createTapsFromTicks(HitObject original) case SliderEventType.Repeat: yield return new Tap { - Path = notePath, + Lane = noteLane, Samples = original.Samples.Select(s => new HitSampleInfo { Bank = s.Bank, @@ -178,11 +178,11 @@ private IEnumerable createTapsFromTicks(HitObject original) private SentakkiHitObject createTapNote(HitObject original, bool twin = false, bool isBreak = false) { - int notePath = getNewPath(twin); + int noteLane = getNewLane(twin); return new Tap { IsBreak = isBreak, - Path = notePath, + Lane = noteLane, Samples = original.Samples, StartTime = original.StartTime, }; diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index 7c18604a0..629ffe0b2 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -84,9 +84,9 @@ public DrawableHold(Hold hitObject) } }); - hitObject.PathBindable.BindValueChanged(r => + hitObject.LaneBindable.BindValueChanged(r => { - Rotation = r.NewValue.GetAngleFromPath(); + Rotation = r.NewValue.GetRotationForLane(); HitArea.NotePath = r.NewValue; }, true); } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs index 83d71ea05..d7d2abd75 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs @@ -31,7 +31,7 @@ public class DrawableSentakkiHitObject : DrawableHitObject // Used for the animation update protected readonly Bindable AnimationDuration = new Bindable(1000); - protected override float SamplePlaybackPosition => (SentakkiExtensions.GetPathPosition(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Path).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + .5f; + protected override float SamplePlaybackPosition => (SentakkiExtensions.GetPositionAlongLane(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Lane).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + .5f; public SentakkiAction[] HitActions { get; set; } = new[] { SentakkiAction.Button1, diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs index 2647ce94c..980bb2299 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTap.cs @@ -49,9 +49,9 @@ public DrawableTap(SentakkiHitObject hitObject) Position = new Vector2(0, -SentakkiPlayfield.INTERSECTDISTANCE), }, }); - hitObject.PathBindable.BindValueChanged(r => + hitObject.LaneBindable.BindValueChanged(r => { - Rotation = r.NewValue.GetAngleFromPath(); + Rotation = r.NewValue.GetRotationForLane(); HitArea.NotePath = r.NewValue; }, true); } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs index 60b51a8ba..0a1eb3d96 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs @@ -63,14 +63,14 @@ public override double StartTime } } - public override int Path + public override int Lane { - get => base.Path; + get => base.Lane; set { - base.Path = value; - Head.Path = value; - Tail.Path = value; + base.Lane = value; + Head.Lane = value; + Tail.Lane = value; } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs index af3cf8c80..38570cc97 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs @@ -19,11 +19,11 @@ public abstract class SentakkiHitObject : HitObject public virtual Color4 NoteColor => IsBreak ? Color4.OrangeRed : (HasTwin ? Color4.Gold : Color4Extensions.FromHex("ff0064")); - public readonly BindableInt PathBindable = new BindableInt(0); - public virtual int Path + public readonly BindableInt LaneBindable = new BindableInt(0); + public virtual int Lane { - get => PathBindable.Value; - set => PathBindable.Value = value; + get => LaneBindable.Value; + set => LaneBindable.Value = value; } protected override HitWindows CreateHitWindows() => new SentakkiHitWindows(); diff --git a/osu.Game.Rulesets.Sentakki/SentakkiExtensions.cs b/osu.Game.Rulesets.Sentakki/SentakkiExtensions.cs index 038843901..293114d38 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiExtensions.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiExtensions.cs @@ -6,13 +6,13 @@ namespace osu.Game.Rulesets.Sentakki { public static class SentakkiExtensions { - public static float GetAngleFromPath(this int path) + public static float GetRotationForLane(this int lane) { - while (path < 0) path += 8; - path %= 8; - return SentakkiPlayfield.PATHANGLES[path]; + while (lane < 0) lane += 8; + lane %= 8; + return SentakkiPlayfield.LANEANGLES[lane]; } - public static Vector2 GetPathPosition(float distance, int path) => GetCircularPosition(distance, path.GetAngleFromPath()); + public static Vector2 GetPositionAlongLane(float distance, int lane) => GetCircularPosition(distance, lane.GetRotationForLane()); public static Vector2 GetCircularPosition(float distance, float angle) { @@ -21,15 +21,15 @@ public static Vector2 GetCircularPosition(float distance, float angle) public static float GetDegreesFromPosition(this Vector2 target, Vector2 self) => (float)MathHelper.RadiansToDegrees(Math.Atan2(target.X - self.X, target.Y - self.Y)); - public static int GetNotePathFromDegrees(this float degrees) + public static int GetNoteLaneFromDegrees(this float degrees) { if (degrees < 0) degrees += 360; if (degrees >= 360) degrees %= 360; int result = 0; - for (int i = 0; i < SentakkiPlayfield.PATHANGLES.Length; ++i) + for (int i = 0; i < SentakkiPlayfield.LANEANGLES.Length; ++i) { - if (SentakkiPlayfield.PATHANGLES[i] - degrees >= -22.5f && SentakkiPlayfield.PATHANGLES[i] - degrees <= 22.5f) + if (SentakkiPlayfield.LANEANGLES[i] - degrees >= -22.5f && SentakkiPlayfield.LANEANGLES[i] - degrees <= 22.5f) result = i; } return result; diff --git a/osu.Game.Rulesets.Sentakki/UI/Components/SentakkiRing.cs b/osu.Game.Rulesets.Sentakki/UI/Components/SentakkiRing.cs index f7205d91a..b09843997 100644 --- a/osu.Game.Rulesets.Sentakki/UI/Components/SentakkiRing.cs +++ b/osu.Game.Rulesets.Sentakki/UI/Components/SentakkiRing.cs @@ -97,7 +97,7 @@ public SentakkiRing() }; // Add dots to the actual ring - foreach (float pathAngle in SentakkiPlayfield.PATHANGLES) + foreach (float pathAngle in SentakkiPlayfield.LANEANGLES) { AddInternal(new CircularContainer { diff --git a/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs b/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs index dfd7b3a5c..614b49b5b 100644 --- a/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs +++ b/osu.Game.Rulesets.Sentakki/UI/SentakkiPlayfield.cs @@ -31,17 +31,17 @@ public class SentakkiPlayfield : Playfield, IRequireHighFrequencyMousePosition public static readonly float INTERSECTDISTANCE = 296.5f; public static readonly float NOTESTARTDISTANCE = 66f; - public static readonly float[] PATHANGLES = - { - 22.5f, - 67.5f, - 112.5f, - 157.5f, - 202.5f, - 247.5f, - 292.5f, - 337.5f - }; + public static readonly float[] LANEANGLES = + { + 22.5f, + 67.5f, + 112.5f, + 157.5f, + 202.5f, + 247.5f, + 292.5f, + 337.5f + }; // Touch notes always appear above other notes, regardless of start time private readonly TouchNoteProxyContainer touchNoteContainer; @@ -129,8 +129,8 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Position = SentakkiExtensions.GetPathPosition(240, sentakkiObj.HitObject.Path), - Rotation = sentakkiObj.HitObject.Path.GetAngleFromPath(), + Position = SentakkiExtensions.GetPositionAlongLane(240, sentakkiObj.HitObject.Lane), + Rotation = sentakkiObj.HitObject.Lane.GetRotationForLane(), }; break; }