From 01815de675a7346f83ce55a78d64f9c3102e9d5a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 10:25:37 +0900 Subject: [PATCH 01/38] add HitPositionMeter and basic test --- .../TestSceneHitPositionMeter.cs | 49 +++++ .../Skinning/HitPositionMeter.cs | 192 ++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs new file mode 100644 index 0000000000..19ef11403d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD.HitErrorMeters; +using System.Diagnostics; +using System; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneHitPositionMeter : OsuTestScene + { + private DependencyProvidingContainer dependencyContainer = null!; + private ScoreProcessor scoreProcessor = null!; + + private HitPositionMeter hitPositionMeter = null!; + + [SetUpSteps] + public void SetupSteps() => AddStep("Create components", () => + { + var ruleset = new OsuRuleset(); + + scoreProcessor = new ScoreProcessor(ruleset); + Child = dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(ScoreProcessor), scoreProcessor) + } + }; + dependencyContainer.Child = hitPositionMeter = new HitPositionMeter + { + Margin = new MarginPadding + { + Top = 100 + }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(2), + }; + }); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs new file mode 100644 index 0000000000..a60d100f15 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs @@ -0,0 +1,192 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public partial class HitPositionMeter : HitErrorMeter + { + [Resolved] + private ScoreProcessor processor { get; set; } = null!; + + private Container averagePositionContainer = null!; + private Vector2 averagePosition = Vector2.Zero; + + private readonly DrawablePool hitPosisionPool = new DrawablePool(20); + private Container hitPosisionsContainer = null!; + + private const float arrow_width = 3f; + + private float objectRadis; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public HitPositionMeter() + { + AutoSizeAxes = Axes.Both; + AlwaysPresent = true; + } + + [BackgroundDependencyLoader] + private void load(IBindable beatmap) + { + InternalChild = new Container + { + Height = 100, + Width = 100, + Children = new Drawable[] + { + hitPosisionPool, + new Circle + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.3f, + Colour = Colour4.Gray + }, + hitPosisionsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + averagePositionContainer = new Container + { + RelativePositionAxes = Axes.Both, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = arrow_width, + Height = arrow_width * 4, + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = arrow_width, + Height = arrow_width * 4, + Rotation = 90 + } + } + } + } + }; + + objectRadis = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); + } + + protected override void OnNewJudgement(JudgementResult _) + { + var lastHit = processor.HitEvents.Last(); + if (lastHit.Position == null) return; + + var relativeHitPosition = (lastHit.Position.Value - ((OsuHitObject)lastHit.HitObject).StackedPosition) / objectRadis / 2; + + hitPosisionPool.Get(drawableHit => + { + drawableHit.X = relativeHitPosition.X; + drawableHit.Y = relativeHitPosition.Y; + drawableHit.Colour = getColourForPosition(relativeHitPosition); + + hitPosisionsContainer.Add(drawableHit); + }); + + averagePositionContainer.MoveTo(averagePosition = (relativeHitPosition + averagePosition) / 2, 800, Easing.OutQuint); + } + + private Color4 getColourForPosition(Vector2 position) + { + switch (Vector2.Distance(position, Vector2.Zero)) + { + case >= 0.35f: + return colours.Yellow; + + case >= 0.2f: + return colours.Green; + + default: + return colours.Blue; + } + } + + public override void Clear() + { + averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); + hitPosisionsContainer.Clear(); + } + + private partial class HitPosition : PoolableDrawable + { + private const float small_arrow_width = 1.5f; + + public HitPosition() + { + AutoSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = small_arrow_width, + Height = small_arrow_width * 4, + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = small_arrow_width, + Height = small_arrow_width * 4, + Rotation = 90 + } + }; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + const int judgement_fade_in_duration = 100; + const int judgement_fade_out_duration = 5000; + + Alpha = 0; + + this + .FadeTo(1f, judgement_fade_in_duration, Easing.OutQuint) + .Then() + .FadeOut(judgement_fade_out_duration) + .Expire(); + } + } + } +} From de65e90abfd0dd1d55dd89c94b81cba0096f3701 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 10:26:07 +0800 Subject: [PATCH 02/38] typo, add border, remove usage of scoreprocessor --- .../Skinning/HitPositionMeter.cs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs index a60d100f15..71bc94be87 100644 --- a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; @@ -25,18 +26,15 @@ namespace osu.Game.Rulesets.Osu.Skinning { public partial class HitPositionMeter : HitErrorMeter { - [Resolved] - private ScoreProcessor processor { get; set; } = null!; - private Container averagePositionContainer = null!; private Vector2 averagePosition = Vector2.Zero; - private readonly DrawablePool hitPosisionPool = new DrawablePool(20); - private Container hitPosisionsContainer = null!; + private readonly DrawablePool hitPositionPool = new DrawablePool(20); + private Container hitPositionsContainer = null!; private const float arrow_width = 3f; - private float objectRadis; + private float objectRadius; [Resolved] private OsuColour colours { get; set; } = null!; @@ -56,14 +54,21 @@ namespace osu.Game.Rulesets.Osu.Skinning Width = 100, Children = new Drawable[] { - hitPosisionPool, - new Circle + hitPositionPool, + new CircularContainer { + BorderColour = Colour4.White, + Masking = true, + BorderThickness = 2, RelativeSizeAxes = Axes.Both, - Alpha = 0.3f, - Colour = Colour4.Gray + Child = new Box + { + Colour = Colour4.Gray, + Alpha = 0.3f, + RelativeSizeAxes = Axes.Both + }, }, - hitPosisionsContainer = new Container + hitPositionsContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -97,23 +102,24 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; - objectRadis = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); + objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); } - protected override void OnNewJudgement(JudgementResult _) + protected override void OnNewJudgement(JudgementResult judgement) { - var lastHit = processor.HitEvents.Last(); - if (lastHit.Position == null) return; + if (judgement is not OsuHitCircleJudgementResult circleJudgement) return; - var relativeHitPosition = (lastHit.Position.Value - ((OsuHitObject)lastHit.HitObject).StackedPosition) / objectRadis / 2; + if (circleJudgement.CursorPositionAtHit == null) return; - hitPosisionPool.Get(drawableHit => + var relativeHitPosition = (circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2; + + hitPositionPool.Get(drawableHit => { drawableHit.X = relativeHitPosition.X; drawableHit.Y = relativeHitPosition.Y; drawableHit.Colour = getColourForPosition(relativeHitPosition); - hitPosisionsContainer.Add(drawableHit); + hitPositionsContainer.Add(drawableHit); }); averagePositionContainer.MoveTo(averagePosition = (relativeHitPosition + averagePosition) / 2, 800, Easing.OutQuint); @@ -137,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public override void Clear() { averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); - hitPosisionsContainer.Clear(); + hitPositionsContainer.Clear(); } private partial class HitPosition : PoolableDrawable From eae4227c5a1a8b56afb4fad80243bf35c30a1159 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 11:17:41 +0800 Subject: [PATCH 03/38] add gameObject to test --- .../TestSceneHitPositionMeter.cs | 107 ++++++++++++++++-- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs index 19ef11403d..2d3055bc82 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs @@ -1,28 +1,43 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. + +using System; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD.HitErrorMeters; -using System.Diagnostics; -using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Tests.Visual; using osuTK; +using osuTK.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Framework.Threading; namespace osu.Game.Rulesets.Osu.Tests { - public partial class TestSceneHitPositionMeter : OsuTestScene + public partial class TestSceneHitPositionMeter : OsuManualInputManagerTestScene { private DependencyProvidingContainer dependencyContainer = null!; private ScoreProcessor scoreProcessor = null!; - private HitPositionMeter hitPositionMeter = null!; + private TestHitPositionMeter hitPositionMeter = null!; + + private CircularContainer gameObject = null!; + + private ScheduledDelegate? automaticAdditionDelegate; [SetUpSteps] public void SetupSteps() => AddStep("Create components", () => { + automaticAdditionDelegate?.Cancel(); + automaticAdditionDelegate = null; + var ruleset = new OsuRuleset(); scoreProcessor = new ScoreProcessor(ruleset); @@ -34,16 +49,86 @@ namespace osu.Game.Rulesets.Osu.Tests (typeof(ScoreProcessor), scoreProcessor) } }; - dependencyContainer.Child = hitPositionMeter = new HitPositionMeter + dependencyContainer.Children = new Drawable[] { - Margin = new MarginPadding + hitPositionMeter = new TestHitPositionMeter { - Top = 100 + Margin = new MarginPadding + { + Top = 100 + }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(2), }, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Scale = new Vector2(2), + + gameObject = new CircularContainer + { + Size = new Vector2(100), + Position = new Vector2(256, 192), + Colour = Color4.Yellow, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(4), + } + } + } }; }); + + protected override bool OnMouseDown(MouseDownEvent e) + { + hitPositionMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(50)); + return true; + } + + [Test] + public void TestManyHitPointsAutomatic() + { + AddStep("add scheduled delegate", () => + { + automaticAdditionDelegate = Scheduler.AddDelayed(() => + { + var randomPos = new Vector2( + RNG.NextSingle(0, 100), + RNG.NextSingle(0, 100)); + + hitPositionMeter.AddPoint(randomPos - new Vector2(50)); + InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos)); + }, 1, true); + }); + + AddWaitStep("wait for some hit points", 10); + } + + [Test] + public void TestManualPlacement() + { + AddStep("return user input", () => InputManager.UseParentInput = true); + } + + private partial class TestHitPositionMeter : HitPositionMeter + { + public void AddPoint(Vector2 position) + { + OnNewJudgement(new OsuHitCircleJudgementResult(new HitCircle(), new OsuJudgement()) + { + CursorPositionAtHit = position + }); + } + } } } From 18d3e9154ffcedaf7971675b8aed422b789f759b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 12:47:21 +0900 Subject: [PATCH 04/38] add color for miss but miss HitEvent have no position? --- osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs index 71bc94be87..43cc09f259 100644 --- a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,10 +13,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.HitErrorMeters; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -129,6 +124,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { switch (Vector2.Distance(position, Vector2.Zero)) { + case >= 0.5f: + return colours.Red; + case >= 0.35f: return colours.Yellow; From 3e5663388251bf32e5f80ddc5efa8e5f9a619dfd Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 12:56:23 +0900 Subject: [PATCH 05/38] adjust object scale --- .../TestSceneHitPositionMeter.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs index 2d3055bc82..bff5d33ee7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests gameObject = new CircularContainer { - Size = new Vector2(100), + Size = new Vector2(108), Position = new Vector2(256, 192), Colour = Color4.Yellow, Masking = true, @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool OnMouseDown(MouseDownEvent e) { - hitPositionMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(50)); + hitPositionMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(54)); return true; } @@ -103,10 +103,10 @@ namespace osu.Game.Rulesets.Osu.Tests automaticAdditionDelegate = Scheduler.AddDelayed(() => { var randomPos = new Vector2( - RNG.NextSingle(0, 100), - RNG.NextSingle(0, 100)); + RNG.NextSingle(0, 108), + RNG.NextSingle(0, 108)); - hitPositionMeter.AddPoint(randomPos - new Vector2(50)); + hitPositionMeter.AddPoint(randomPos - new Vector2(54)); InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos)); }, 1, true); }); From 72d97f4ad6c33266f136a8fc53fce7ea5bc8ed51 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 12:56:31 +0900 Subject: [PATCH 06/38] + to x --- osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs index 43cc09f259..5dfa8423be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs @@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre, Width = small_arrow_width, Height = small_arrow_width * 4, + Rotation = -45 }, new Circle { @@ -171,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre, Width = small_arrow_width, Height = small_arrow_width * 4, - Rotation = 90 + Rotation = 45 } }; } From daff00300a10a960d03c48f8c65d3be437f10a72 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 14:16:22 +0900 Subject: [PATCH 07/38] add settings for hit position meter --- .../Skinning/HitPositionMeter.cs | 106 ++++++++++++++---- osu.Game/Localisation/PositionMeterStrings.cs | 64 +++++++++++ 2 files changed, 146 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Localisation/PositionMeterStrings.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs index 5dfa8423be..df7d53392e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs @@ -7,8 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; @@ -19,8 +23,31 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { + [Cached] public partial class HitPositionMeter : HitErrorMeter { + [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.JudgmentSize), nameof(PositionMeterStrings.JudgmentSizeDescription))] + public BindableNumber JudgmentSize { get; } = new BindableNumber(7f) + { + MinValue = 0f, + MaxValue = 12f, + Precision = 1f + }; + + [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.JudgmentStyle), nameof(PositionMeterStrings.JudgmentStyleDescription))] + public Bindable JudgmentStyle { get; } = new Bindable(); + + [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.AverageSize), nameof(PositionMeterStrings.AverageSizeDescription))] + public BindableNumber AverageSize { get; } = new BindableNumber(12f) + { + MinValue = 7f, + MaxValue = 25f, + Precision = 1f + }; + + [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.AverageStyle), nameof(PositionMeterStrings.AverageStyleDescription))] + public Bindable AverageStyle { get; } = new Bindable(HitPositionStyle.Plus); + private Container averagePositionContainer = null!; private Vector2 averagePosition = Vector2.Zero; @@ -63,34 +90,39 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both }, }, - hitPositionsContainer = new Container + hitPositionsContainer = new UprightAspectMaintainingContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre }, - averagePositionContainer = new Container + new UprightAspectMaintainingContainer { - RelativePositionAxes = Axes.Both, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] + Child = averagePositionContainer = new Container { - new Circle + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = arrow_width, - Height = arrow_width * 4, - }, - new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = arrow_width, - Height = arrow_width * 4, - Rotation = 90 + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + }, + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + Rotation = 90 + } } } } @@ -98,6 +130,9 @@ namespace osu.Game.Rulesets.Osu.Skinning }; objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); + + AverageSize.BindValueChanged(size => averagePositionContainer.Size = new Vector2(size.NewValue), true); + AverageStyle.BindValueChanged(style => averagePositionContainer.Rotation = style.NewValue == HitPositionStyle.Plus ? 0 : 45, true); } protected override void OnNewJudgement(JudgementResult judgement) @@ -146,11 +181,15 @@ namespace osu.Game.Rulesets.Osu.Skinning private partial class HitPosition : PoolableDrawable { - private const float small_arrow_width = 1.5f; + [Resolved] + private HitPositionMeter hitPositionMeter { get; set; } = null!; + + public readonly BindableNumber JudgmentSize = new BindableFloat(); + + public readonly Bindable JudgmentStyle = new Bindable(); public HitPosition() { - AutoSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Anchor = Anchor.Centre; @@ -160,23 +199,33 @@ namespace osu.Game.Rulesets.Osu.Skinning { new Circle { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = small_arrow_width, - Height = small_arrow_width * 4, + Width = 0.25f, Rotation = -45 }, new Circle { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = small_arrow_width, - Height = small_arrow_width * 4, + Width = 0.25f, Rotation = 45 } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + JudgmentSize.BindTo(hitPositionMeter.JudgmentSize); + JudgmentSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); + JudgmentStyle.BindTo(hitPositionMeter.JudgmentStyle); + JudgmentStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); + } + protected override void PrepareForUse() { base.PrepareForUse(); @@ -193,5 +242,14 @@ namespace osu.Game.Rulesets.Osu.Skinning .Expire(); } } + + public enum HitPositionStyle + { + [LocalisableDescription(typeof(PositionMeterStrings), nameof(PositionMeterStrings.StyleX))] + X, + + [LocalisableDescription(typeof(PositionMeterStrings), nameof(PositionMeterStrings.StylePlus))] + Plus + } } } diff --git a/osu.Game/Localisation/PositionMeterStrings.cs b/osu.Game/Localisation/PositionMeterStrings.cs new file mode 100644 index 0000000000..36e7526595 --- /dev/null +++ b/osu.Game/Localisation/PositionMeterStrings.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class PositionMeterStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.HUD.PositionMeterStrings"; + + /// + /// "Judgment position size." + /// + public static LocalisableString JudgmentSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgment position size."); + + /// + /// "How big of judgment position should be." + /// + public static LocalisableString JudgmentSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of judgment position should be."); + + /// + /// "Judgment position style." + /// + public static LocalisableString JudgmentStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgment position style."); + + /// + /// "The style of judgment position." + /// + public static LocalisableString JudgmentStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of judgment position."); + + /// + /// "Average position size." + /// + public static LocalisableString AverageSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position size."); + + /// + /// "How big of average position should be." + /// + public static LocalisableString AverageSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of average position should be."); + + /// + /// "Average position style." + /// + public static LocalisableString AverageStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position style."); + + /// + /// "The style of average position." + /// + public static LocalisableString AverageStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of average position."); + + /// + /// "X" + /// + public static LocalisableString StyleX => new TranslatableString(getKey("style_x"), "X"); + + /// + /// "+" + /// + public static LocalisableString StylePlus => new TranslatableString(getKey("style_plus"), "+"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} From 6fa6de7c27fe06ded1f382767dd591ce0f7f99a6 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 14:29:43 +0900 Subject: [PATCH 08/38] remove empty line --- osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs index bff5d33ee7..b7bf6a78dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. - using System; using osu.Framework.Graphics; using osu.Framework.Testing; From f9076183d09651748a1ac2c843026a0c6b69a2d9 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 20:47:03 +0900 Subject: [PATCH 09/38] rename Hit position to Aim error used in danser and I think this is better --- ...tionMeter.cs => TestSceneAimErrorMeter.cs} | 12 +++++----- .../{HitPositionMeter.cs => AimErrorMeter.cs} | 22 +++++++++---------- ...eterStrings.cs => AimErrorMeterStrings.cs} | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneHitPositionMeter.cs => TestSceneAimErrorMeter.cs} (89%) rename osu.Game.Rulesets.Osu/Skinning/{HitPositionMeter.cs => AimErrorMeter.cs} (89%) rename osu.Game/Localisation/{PositionMeterStrings.cs => AimErrorMeterStrings.cs} (98%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index b7bf6a78dc..b450288124 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -20,12 +20,12 @@ using osu.Framework.Threading; namespace osu.Game.Rulesets.Osu.Tests { - public partial class TestSceneHitPositionMeter : OsuManualInputManagerTestScene + public partial class TestSceneAimErrorMeter : OsuManualInputManagerTestScene { private DependencyProvidingContainer dependencyContainer = null!; private ScoreProcessor scoreProcessor = null!; - private TestHitPositionMeter hitPositionMeter = null!; + private TestAimErrorMeter aimErrorMeter = null!; private CircularContainer gameObject = null!; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests }; dependencyContainer.Children = new Drawable[] { - hitPositionMeter = new TestHitPositionMeter + aimErrorMeter = new TestAimErrorMeter { Margin = new MarginPadding { @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool OnMouseDown(MouseDownEvent e) { - hitPositionMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(54)); + aimErrorMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(54)); return true; } @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests RNG.NextSingle(0, 108), RNG.NextSingle(0, 108)); - hitPositionMeter.AddPoint(randomPos - new Vector2(54)); + aimErrorMeter.AddPoint(randomPos - new Vector2(54)); InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos)); }, 1, true); }); @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("return user input", () => InputManager.UseParentInput = true); } - private partial class TestHitPositionMeter : HitPositionMeter + private partial class TestAimErrorMeter : AimErrorMeter { public void AddPoint(Vector2 position) { diff --git a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs similarity index 89% rename from osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs rename to osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index df7d53392e..47a60b72e4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/HitPositionMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -24,9 +24,9 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { [Cached] - public partial class HitPositionMeter : HitErrorMeter + public partial class AimErrorMeter : HitErrorMeter { - [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.JudgmentSize), nameof(PositionMeterStrings.JudgmentSizeDescription))] + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgmentSize), nameof(AimErrorMeterStrings.JudgmentSizeDescription))] public BindableNumber JudgmentSize { get; } = new BindableNumber(7f) { MinValue = 0f, @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Osu.Skinning Precision = 1f }; - [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.JudgmentStyle), nameof(PositionMeterStrings.JudgmentStyleDescription))] + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgmentStyle), nameof(AimErrorMeterStrings.JudgmentStyleDescription))] public Bindable JudgmentStyle { get; } = new Bindable(); - [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.AverageSize), nameof(PositionMeterStrings.AverageSizeDescription))] + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageSize), nameof(AimErrorMeterStrings.AverageSizeDescription))] public BindableNumber AverageSize { get; } = new BindableNumber(12f) { MinValue = 7f, @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Precision = 1f }; - [SettingSource(typeof(PositionMeterStrings), nameof(PositionMeterStrings.AverageStyle), nameof(PositionMeterStrings.AverageStyleDescription))] + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageStyle), nameof(AimErrorMeterStrings.AverageStyleDescription))] public Bindable AverageStyle { get; } = new Bindable(HitPositionStyle.Plus); private Container averagePositionContainer = null!; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [Resolved] private OsuColour colours { get; set; } = null!; - public HitPositionMeter() + public AimErrorMeter() { AutoSizeAxes = Axes.Both; AlwaysPresent = true; @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private partial class HitPosition : PoolableDrawable { [Resolved] - private HitPositionMeter hitPositionMeter { get; set; } = null!; + private AimErrorMeter aimErrorMeter { get; set; } = null!; public readonly BindableNumber JudgmentSize = new BindableFloat(); @@ -220,9 +220,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - JudgmentSize.BindTo(hitPositionMeter.JudgmentSize); + JudgmentSize.BindTo(aimErrorMeter.JudgmentSize); JudgmentSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); - JudgmentStyle.BindTo(hitPositionMeter.JudgmentStyle); + JudgmentStyle.BindTo(aimErrorMeter.JudgmentStyle); JudgmentStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); } @@ -245,10 +245,10 @@ namespace osu.Game.Rulesets.Osu.Skinning public enum HitPositionStyle { - [LocalisableDescription(typeof(PositionMeterStrings), nameof(PositionMeterStrings.StyleX))] + [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StyleX))] X, - [LocalisableDescription(typeof(PositionMeterStrings), nameof(PositionMeterStrings.StylePlus))] + [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StylePlus))] Plus } } diff --git a/osu.Game/Localisation/PositionMeterStrings.cs b/osu.Game/Localisation/AimErrorMeterStrings.cs similarity index 98% rename from osu.Game/Localisation/PositionMeterStrings.cs rename to osu.Game/Localisation/AimErrorMeterStrings.cs index 36e7526595..31e64a2add 100644 --- a/osu.Game/Localisation/PositionMeterStrings.cs +++ b/osu.Game/Localisation/AimErrorMeterStrings.cs @@ -5,7 +5,7 @@ using osu.Framework.Localisation; namespace osu.Game.Localisation { - public static class PositionMeterStrings + public static class AimErrorMeterStrings { private const string prefix = @"osu.Game.Resources.Localisation.HUD.PositionMeterStrings"; From c16ef5eac33b6b5c175d32a3f1fee4e99b77da41 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 20:55:05 +0900 Subject: [PATCH 10/38] cleanup --- osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index 47a60b72e4..e64d8565ed 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -49,13 +49,11 @@ namespace osu.Game.Rulesets.Osu.Skinning public Bindable AverageStyle { get; } = new Bindable(HitPositionStyle.Plus); private Container averagePositionContainer = null!; - private Vector2 averagePosition = Vector2.Zero; + private Vector2 averagePosition; private readonly DrawablePool hitPositionPool = new DrawablePool(20); private Container hitPositionsContainer = null!; - private const float arrow_width = 3f; - private float objectRadius; [Resolved] From ad1fdc631d14c784856a9803f063a96758ba2c47 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 20:55:32 +0900 Subject: [PATCH 11/38] use FadeInFromZero to avoid sudden transition --- osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index e64d8565ed..1d474ab587 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -231,10 +231,8 @@ namespace osu.Game.Rulesets.Osu.Skinning const int judgement_fade_in_duration = 100; const int judgement_fade_out_duration = 5000; - Alpha = 0; - this - .FadeTo(1f, judgement_fade_in_duration, Easing.OutQuint) + .FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint) .Then() .FadeOut(judgement_fade_out_duration) .Expire(); From 380c3d044465e7bd6e2d0d197d7b35b7c3086b3d Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 10 Dec 2023 21:07:51 +0900 Subject: [PATCH 12/38] Zoom animation --- osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index 1d474ab587..dd4d785c1b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); JudgmentSize.BindTo(aimErrorMeter.JudgmentSize); - JudgmentSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); + JudgmentSize.BindValueChanged(size => Size = new Vector2(size.NewValue)); JudgmentStyle.BindTo(aimErrorMeter.JudgmentStyle); JudgmentStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); } @@ -232,7 +232,9 @@ namespace osu.Game.Rulesets.Osu.Skinning const int judgement_fade_out_duration = 5000; this + .ResizeTo(new Vector2(0)) .FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint) + .ResizeTo(new Vector2(JudgmentSize.Value), judgement_fade_in_duration, Easing.OutQuint) .Then() .FadeOut(judgement_fade_out_duration) .Expire(); From 6eda09aff409d6bf6b7560daf8be233868b135d5 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 11 Dec 2023 07:50:13 +0900 Subject: [PATCH 13/38] Judgment -> Judgement --- .../Skinning/AimErrorMeter.cs | 22 +++++++++---------- osu.Game/Localisation/AimErrorMeterStrings.cs | 16 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index dd4d785c1b..f4a10dc505 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -26,16 +26,16 @@ namespace osu.Game.Rulesets.Osu.Skinning [Cached] public partial class AimErrorMeter : HitErrorMeter { - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgmentSize), nameof(AimErrorMeterStrings.JudgmentSizeDescription))] - public BindableNumber JudgmentSize { get; } = new BindableNumber(7f) + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgementSize), nameof(AimErrorMeterStrings.JudgementSizeDescription))] + public BindableNumber JudgementSize { get; } = new BindableNumber(7f) { MinValue = 0f, MaxValue = 12f, Precision = 1f }; - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgmentStyle), nameof(AimErrorMeterStrings.JudgmentStyleDescription))] - public Bindable JudgmentStyle { get; } = new Bindable(); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgementStyle), nameof(AimErrorMeterStrings.JudgementStyleDescription))] + public Bindable JudgementStyle { get; } = new Bindable(); [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageSize), nameof(AimErrorMeterStrings.AverageSizeDescription))] public BindableNumber AverageSize { get; } = new BindableNumber(12f) @@ -182,9 +182,9 @@ namespace osu.Game.Rulesets.Osu.Skinning [Resolved] private AimErrorMeter aimErrorMeter { get; set; } = null!; - public readonly BindableNumber JudgmentSize = new BindableFloat(); + public readonly BindableNumber JudgementSize = new BindableFloat(); - public readonly Bindable JudgmentStyle = new Bindable(); + public readonly Bindable JudgementStyle = new Bindable(); public HitPosition() { @@ -218,10 +218,10 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - JudgmentSize.BindTo(aimErrorMeter.JudgmentSize); - JudgmentSize.BindValueChanged(size => Size = new Vector2(size.NewValue)); - JudgmentStyle.BindTo(aimErrorMeter.JudgmentStyle); - JudgmentStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); + JudgementSize.BindTo(aimErrorMeter.JudgementSize); + JudgementSize.BindValueChanged(size => Size = new Vector2(size.NewValue)); + JudgementStyle.BindTo(aimErrorMeter.JudgementStyle); + JudgementStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); } protected override void PrepareForUse() @@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Skinning this .ResizeTo(new Vector2(0)) .FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint) - .ResizeTo(new Vector2(JudgmentSize.Value), judgement_fade_in_duration, Easing.OutQuint) + .ResizeTo(new Vector2(JudgementSize.Value), judgement_fade_in_duration, Easing.OutQuint) .Then() .FadeOut(judgement_fade_out_duration) .Expire(); diff --git a/osu.Game/Localisation/AimErrorMeterStrings.cs b/osu.Game/Localisation/AimErrorMeterStrings.cs index 31e64a2add..0b6d46d902 100644 --- a/osu.Game/Localisation/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/AimErrorMeterStrings.cs @@ -10,24 +10,24 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.HUD.PositionMeterStrings"; /// - /// "Judgment position size." + /// "Judgement position size." /// - public static LocalisableString JudgmentSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgment position size."); + public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position size."); /// - /// "How big of judgment position should be." + /// "How big of judgement position should be." /// - public static LocalisableString JudgmentSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of judgment position should be."); + public static LocalisableString JudgementSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of judgement position should be."); /// - /// "Judgment position style." + /// "Judgement position style." /// - public static LocalisableString JudgmentStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgment position style."); + public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position style."); /// - /// "The style of judgment position." + /// "The style of judgement position." /// - public static LocalisableString JudgmentStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of judgment position."); + public static LocalisableString JudgementStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of judgement position."); /// /// "Average position size." From 77a2ac8f42f4cb836ee3ad51fc643bddd04adb0a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 11 Dec 2023 07:58:41 +0900 Subject: [PATCH 14/38] remove dot at the end of label string --- osu.Game/Localisation/AimErrorMeterStrings.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Localisation/AimErrorMeterStrings.cs b/osu.Game/Localisation/AimErrorMeterStrings.cs index 0b6d46d902..27399fd432 100644 --- a/osu.Game/Localisation/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/AimErrorMeterStrings.cs @@ -10,9 +10,9 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.HUD.PositionMeterStrings"; /// - /// "Judgement position size." + /// "Judgement position size" /// - public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position size."); + public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position size"); /// /// "How big of judgement position should be." @@ -20,9 +20,9 @@ namespace osu.Game.Localisation public static LocalisableString JudgementSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of judgement position should be."); /// - /// "Judgement position style." + /// "Judgement position style" /// - public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position style."); + public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position style"); /// /// "The style of judgement position." @@ -30,9 +30,9 @@ namespace osu.Game.Localisation public static LocalisableString JudgementStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of judgement position."); /// - /// "Average position size." + /// "Average position size" /// - public static LocalisableString AverageSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position size."); + public static LocalisableString AverageSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position size"); /// /// "How big of average position should be." @@ -40,9 +40,9 @@ namespace osu.Game.Localisation public static LocalisableString AverageSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of average position should be."); /// - /// "Average position style." + /// "Average position style" /// - public static LocalisableString AverageStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position style."); + public static LocalisableString AverageStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position style"); /// /// "The style of average position." From 31956818056d0d564e8dfaa3fbf6de2cd3001702 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 13 Dec 2023 18:59:03 +0800 Subject: [PATCH 15/38] fix getkey isn't match the name --- osu.Game/Localisation/AimErrorMeterStrings.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Localisation/AimErrorMeterStrings.cs b/osu.Game/Localisation/AimErrorMeterStrings.cs index 27399fd432..764664aedc 100644 --- a/osu.Game/Localisation/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/AimErrorMeterStrings.cs @@ -12,42 +12,42 @@ namespace osu.Game.Localisation /// /// "Judgement position size" /// - public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position size"); + public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_size"), "Judgement position size"); /// /// "How big of judgement position should be." /// - public static LocalisableString JudgementSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of judgement position should be."); + public static LocalisableString JudgementSizeDescription => new TranslatableString(getKey("judgement_size_description"), "How big of judgement position should be."); /// /// "Judgement position style" /// - public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement position style"); + public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_style"), "Judgement position style"); /// /// "The style of judgement position." /// - public static LocalisableString JudgementStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of judgement position."); + public static LocalisableString JudgementStyleDescription => new TranslatableString(getKey("judgement_style_description"), "The style of judgement position."); /// /// "Average position size" /// - public static LocalisableString AverageSize => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position size"); + public static LocalisableString AverageSize => new TranslatableString(getKey(@"average_size"), "Average position size"); /// /// "How big of average position should be." /// - public static LocalisableString AverageSizeDescription => new TranslatableString(getKey("judgement_line_thickness"), "How big of average position should be."); + public static LocalisableString AverageSizeDescription => new TranslatableString(getKey("average_size_description"), "How big of average position should be."); /// /// "Average position style" /// - public static LocalisableString AverageStyle => new TranslatableString(getKey(@"judgement_line_thickness"), "Average position style"); + public static LocalisableString AverageStyle => new TranslatableString(getKey(@"average_style"), "Average position style"); /// /// "The style of average position." /// - public static LocalisableString AverageStyleDescription => new TranslatableString(getKey("judgement_line_thickness"), "The style of average position."); + public static LocalisableString AverageStyleDescription => new TranslatableString(getKey("average_style_description"), "The style of average position."); /// /// "X" From 07d81c08248c02b604ace22ba2767eb41aaeceb0 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 13 Dec 2023 19:00:24 +0800 Subject: [PATCH 16/38] move `AimErrorMeterStrings` to `HUD` --- osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs | 2 +- osu.Game/Localisation/{ => HUD}/AimErrorMeterStrings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/Localisation/{ => HUD}/AimErrorMeterStrings.cs (98%) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index f4a10dc505..0667171dae 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -12,7 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Localisation; +using osu.Game.Localisation.HUD; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; diff --git a/osu.Game/Localisation/AimErrorMeterStrings.cs b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs similarity index 98% rename from osu.Game/Localisation/AimErrorMeterStrings.cs rename to osu.Game/Localisation/HUD/AimErrorMeterStrings.cs index 764664aedc..81344da2aa 100644 --- a/osu.Game/Localisation/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs @@ -3,7 +3,7 @@ using osu.Framework.Localisation; -namespace osu.Game.Localisation +namespace osu.Game.Localisation.HUD { public static class AimErrorMeterStrings { From f61cb3caa7dc921c46cd999522fae6ceaa9e8fa5 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 13 Dec 2023 19:06:35 +0800 Subject: [PATCH 17/38] clear transforms and returned to pool after `Clear()` from #25747 --- osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index 0667171dae..70ec88a11e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -174,7 +174,12 @@ namespace osu.Game.Rulesets.Osu.Skinning public override void Clear() { averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); - hitPositionsContainer.Clear(); + + foreach (var h in hitPositionsContainer) + { + h.ClearTransforms(); + h.Expire(); + } } private partial class HitPosition : PoolableDrawable From 814f39058ea0261f4b97e5b01fc1e858e6fbbba3 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 13 Dec 2023 19:07:39 +0800 Subject: [PATCH 18/38] fix `prefix` in `AimErrorMeterStrings` not corrently --- osu.Game/Localisation/HUD/AimErrorMeterStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs index 81344da2aa..b46e0ba380 100644 --- a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation.HUD { public static class AimErrorMeterStrings { - private const string prefix = @"osu.Game.Resources.Localisation.HUD.PositionMeterStrings"; + private const string prefix = @"osu.Game.Resources.Localisation.HUD.AimErrorMeterStrings"; /// /// "Judgement position size" From 1cd2331d28259e623e67db58dbead0bdeac6f944 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 25 Jan 2024 17:27:22 +0900 Subject: [PATCH 19/38] expose `FindRelativeHitPosition` method --- .../Statistics/AccuracyHeatmap.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 83bab7dc01..5c2ae474e0 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -225,10 +225,23 @@ namespace osu.Game.Rulesets.Osu.Statistics if (pointGrid.Content.Count == 0) return; + Vector2 localPoint = FindRelativeHitPosition(start, end, hitPoint, radius, new Vector2(points_per_dimension - 1) / 2, inner_portion, rotation); + + // Find the most relevant hit point. + int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); + int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); + + PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); + + bufferedGrid.ForceRedraw(); + } + + public static Vector2 FindRelativeHitPosition(Vector2 start, Vector2 end, Vector2 hitPoint, float radius, Vector2 localCentre, float localRadius, float rotation) + { double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; // Distance between the hit point and the end point. // Consider two objects placed horizontally, with the start on the left and the end on the right. // The above calculated the angle between {end, start}, and the angle between {end, hitPoint}, in the form: @@ -249,17 +262,8 @@ namespace osu.Game.Rulesets.Osu.Statistics double rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); - Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; - float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. - Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; - - // Find the most relevant hit point. - int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); - int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); - - PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); - - bufferedGrid.ForceRedraw(); + localRadius = localCentre.X * localRadius * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. + return localCentre + localRadius * rotatedCoordinate; } private partial class HitPoint : Circle From 5ffb92b638b59bf66e23badf24b6077bce1e8ff6 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 25 Jan 2024 17:35:53 +0900 Subject: [PATCH 20/38] Add new hit position style, change appearance 1. round the hit position that it will not beyond meter range. 2. add relative position style. - in relative style, rotation can be apply that adjust the relative direction, in Absolute will use `UprightAspectMaintainingContai`ner to prevent rotate because it will cause confusing and meaningless. 3. use the cross-style in https://github.com/ppy/osu/pull/25716#issuecomment-1848974233 --- .../Skinning/AimErrorMeter.cs | 317 +++++++++++++++--- 1 file changed, 271 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs index 70ec88a11e..144fa63b10 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,6 +18,8 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; @@ -35,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning }; [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgementStyle), nameof(AimErrorMeterStrings.JudgementStyleDescription))] - public Bindable JudgementStyle { get; } = new Bindable(); + public Bindable JudgementStyle { get; } = new Bindable(); [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageSize), nameof(AimErrorMeterStrings.AverageSizeDescription))] public BindableNumber AverageSize { get; } = new BindableNumber(12f) @@ -46,16 +49,32 @@ namespace osu.Game.Rulesets.Osu.Skinning }; [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageStyle), nameof(AimErrorMeterStrings.AverageStyleDescription))] - public Bindable AverageStyle { get; } = new Bindable(HitPositionStyle.Plus); + public Bindable AverageStyle { get; } = new Bindable(HitStyle.Plus); + + [SettingSource("Position Style")] + public Bindable HitPositionStyle { get; } = new Bindable(); + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; private Container averagePositionContainer = null!; + private Container averagePositionRotateContainer = null!; private Vector2 averagePosition; private readonly DrawablePool hitPositionPool = new DrawablePool(20); private Container hitPositionsContainer = null!; + private Container arrowBackgroundContainer = null!; + private UprightAspectMaintainingContainer rotateFixedContainer = null!; + private Container mainContainer = null!; + private float objectRadius; + private const int max_concurrent_judgements = 30; + + private const float line_thickness = 2; + private const float inner_portion = 0.85f; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -75,12 +94,29 @@ namespace osu.Game.Rulesets.Osu.Skinning Children = new Drawable[] { hitPositionPool, + rotateFixedContainer = new UprightAspectMaintainingContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }; + + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { new CircularContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, BorderColour = Colour4.White, Masking = true, BorderThickness = 2, RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), Child = new Box { Colour = Colour4.Gray, @@ -88,38 +124,136 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both }, }, - hitPositionsContainer = new UprightAspectMaintainingContainer + arrowBackgroundContainer = new Container { - RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + Name = "Arrow Background", + RelativeSizeAxes = Axes.Both, + Rotation = 45, + Alpha = 0f, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = inner_portion + 0.2f, + Width = line_thickness / 2, + }, + new Circle + { + Height = 5f, + Width = line_thickness / 2, + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(-line_thickness / 4), + RelativePositionAxes = Axes.Both, + Y = -(inner_portion + 0.2f) / 2, + Rotation = -45 + }, + new Circle + { + Height = 5f, + Width = line_thickness / 2, + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(-line_thickness / 4), + RelativePositionAxes = Axes.Both, + Y = -(inner_portion + 0.2f) / 2, + Rotation = 45 + } + } }, - new UprightAspectMaintainingContainer + new Container + { + Name = "Cross Background", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.5f, + Width = line_thickness, + Height = inner_portion * 0.9f + }, + new Circle + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.5f, + Width = line_thickness, + Height = inner_portion * 0.9f, + Rotation = 90 + }, + new Circle + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.2f, + Width = line_thickness / 2, + Height = inner_portion * 0.9f, + Rotation = 45 + }, + new Circle + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.2f, + Width = line_thickness / 2, + Height = inner_portion * 0.9f, + Rotation = 135 + }, + } + }, + new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = averagePositionContainer = new Container + Children = new Drawable[] { - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + hitPositionsContainer = new Container { - new Circle + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + averagePositionContainer = new UprightAspectMaintainingContainer + { + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = averagePositionRotateContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 0.25f, - }, - new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.25f, - Rotation = 90 + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + }, + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + Rotation = 90 + } + } } } } @@ -130,7 +264,29 @@ namespace osu.Game.Rulesets.Osu.Skinning objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); AverageSize.BindValueChanged(size => averagePositionContainer.Size = new Vector2(size.NewValue), true); - AverageStyle.BindValueChanged(style => averagePositionContainer.Rotation = style.NewValue == HitPositionStyle.Plus ? 0 : 45, true); + AverageStyle.BindValueChanged(style => averagePositionRotateContainer.Rotation = style.NewValue == HitStyle.Plus ? 0 : 45, true); + + HitPositionStyle.BindValueChanged(s => + { + foreach (var hit in hitPositionsContainer) + { + hit.FadeOut(300).Expire(); + averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); + } + + if (s.NewValue == PositionStyle.Relative) + { + arrowBackgroundContainer.FadeIn(100); + rotateFixedContainer.Remove(mainContainer, false); + AddInternal(mainContainer); + } + else + { + arrowBackgroundContainer.FadeOut(100); + RemoveInternal(mainContainer, false); + rotateFixedContainer.Add(mainContainer); + } + }, true); } protected override void OnNewJudgement(JudgementResult judgement) @@ -139,31 +295,80 @@ namespace osu.Game.Rulesets.Osu.Skinning if (circleJudgement.CursorPositionAtHit == null) return; - var relativeHitPosition = (circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2; + if (hitPositionsContainer.Count > max_concurrent_judgements) + { + const double quick_fade_time = 300; + + // check with a bit of lenience to avoid precision error in comparison. + var old = hitPositionsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); + + if (old != null) + { + old.ClearTransforms(); + old.FadeOut(quick_fade_time).Expire(); + } + } + + Vector2 hitPosition; + + if (HitPositionStyle.Value == PositionStyle.Relative && scoreProcessor.HitEvents.LastOrDefault().LastHitObject != null) + { + var currentHitEvent = scoreProcessor.HitEvents.Last(); + + hitPosition = AccuracyHeatmap.FindRelativeHitPosition(((OsuHitObject)currentHitEvent.LastHitObject).StackedEndPosition, ((OsuHitObject)currentHitEvent.HitObject).StackedEndPosition, + circleJudgement.CursorPositionAtHit.Value, objectRadius, new Vector2(0.5f), inner_portion, 45) - new Vector2(0.5f); + } + else + { + hitPosition = roundPosition((circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2 * inner_portion); + } hitPositionPool.Get(drawableHit => { - drawableHit.X = relativeHitPosition.X; - drawableHit.Y = relativeHitPosition.Y; - drawableHit.Colour = getColourForPosition(relativeHitPosition); + drawableHit.X = hitPosition.X; + drawableHit.Y = hitPosition.Y; + drawableHit.Colour = getColourForPosition(hitPosition); hitPositionsContainer.Add(drawableHit); }); - averagePositionContainer.MoveTo(averagePosition = (relativeHitPosition + averagePosition) / 2, 800, Easing.OutQuint); + averagePositionContainer.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); + } + + private static Vector2 roundPosition(Vector2 position) + { + if (position.X > 0.5f) + { + position.X = 0.5f; + } + else if (position.X < -0.5f) + { + position.X = -0.5f; + } + + if (position.Y > 0.5f) + { + position.Y = 0.5f; + } + else if (position.Y < -0.5f) + { + position.Y = -0.5f; + } + + return position; } private Color4 getColourForPosition(Vector2 position) { switch (Vector2.Distance(position, Vector2.Zero)) { - case >= 0.5f: + case >= 0.5f * inner_portion: return colours.Red; - case >= 0.35f: + case >= 0.35f * inner_portion: return colours.Yellow; - case >= 0.2f: + case >= 0.2f * inner_portion: return colours.Green; default: @@ -189,7 +394,9 @@ namespace osu.Game.Rulesets.Osu.Skinning public readonly BindableNumber JudgementSize = new BindableFloat(); - public readonly Bindable JudgementStyle = new Bindable(); + public readonly Bindable JudgementStyle = new Bindable(); + + private readonly Container content; public HitPosition() { @@ -198,23 +405,35 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChild = new UprightAspectMaintainingContainer { - new Circle + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = content = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 0.25f, - Rotation = -45 - }, - new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.25f, - Rotation = 45 + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + Rotation = -45 + }, + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.25f, + Rotation = 45 + } + } } }; } @@ -224,9 +443,9 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); JudgementSize.BindTo(aimErrorMeter.JudgementSize); - JudgementSize.BindValueChanged(size => Size = new Vector2(size.NewValue)); + JudgementSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); JudgementStyle.BindTo(aimErrorMeter.JudgementStyle); - JudgementStyle.BindValueChanged(style => Rotation = style.NewValue == HitPositionStyle.X ? 0 : 45); + JudgementStyle.BindValueChanged(style => content.Rotation = style.NewValue == HitStyle.X ? 0 : 45, true); } protected override void PrepareForUse() @@ -246,7 +465,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - public enum HitPositionStyle + public enum HitStyle { [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StyleX))] X, @@ -254,5 +473,11 @@ namespace osu.Game.Rulesets.Osu.Skinning [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StylePlus))] Plus } + + public enum PositionStyle + { + Absolute, + Relative, + } } } From 4e0dca69eddeaf20e05db987160faddc17a0430a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 25 Jan 2024 17:45:56 +0900 Subject: [PATCH 21/38] move AimErrorMeter to HUD --- osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs | 2 +- osu.Game.Rulesets.Osu/{Skinning => HUD}/AimErrorMeter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/{Skinning => HUD}/AimErrorMeter.cs (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index b450288124..080c60ecae 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -7,7 +7,6 @@ using osu.Framework.Testing; using osu.Game.Rulesets.Scoring; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -17,6 +16,7 @@ using osu.Game.Rulesets.Osu.Objects; using NUnit.Framework; using osu.Framework.Utils; using osu.Framework.Threading; +using osu.Game.Rulesets.Osu.HUD; namespace osu.Game.Rulesets.Osu.Tests { diff --git a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs rename to osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 144fa63b10..891723a95a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -24,7 +24,7 @@ using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Osu.Skinning +namespace osu.Game.Rulesets.Osu.HUD { [Cached] public partial class AimErrorMeter : HitErrorMeter From d22435b55f7dc230f0bc609b5b16ac7c312bc1cf Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 25 Jan 2024 22:57:43 +0900 Subject: [PATCH 22/38] rename param --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 5c2ae474e0..5d950c60b2 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Osu.Statistics bufferedGrid.ForceRedraw(); } - public static Vector2 FindRelativeHitPosition(Vector2 start, Vector2 end, Vector2 hitPoint, float radius, Vector2 localCentre, float localRadius, float rotation) + public static Vector2 FindRelativeHitPosition(Vector2 start, Vector2 end, Vector2 hitPoint, float radius, Vector2 localCentre, float innerPortion, float rotation) { double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. @@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.Osu.Statistics double rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); - localRadius = localCentre.X * localRadius * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. + float localRadius = localCentre.X * innerPortion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. return localCentre + localRadius * rotatedCoordinate; } From 9e3c7e2ca997e70b9f322a13e726496ba1c61c92 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 26 Jan 2024 00:43:37 +0900 Subject: [PATCH 23/38] Rewrite some text and names --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 55 ++++++++++--------- .../Localisation/HUD/AimErrorMeterStrings.cs | 36 +++++++++--- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 891723a95a..81b22670e0 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -29,16 +29,16 @@ namespace osu.Game.Rulesets.Osu.HUD [Cached] public partial class AimErrorMeter : HitErrorMeter { - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgementSize), nameof(AimErrorMeterStrings.JudgementSizeDescription))] - public BindableNumber JudgementSize { get; } = new BindableNumber(7f) + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitPositionSize), nameof(AimErrorMeterStrings.HitPositionSizeDescription))] + public BindableNumber HitPositionSize { get; } = new BindableNumber(7f) { MinValue = 0f, MaxValue = 12f, Precision = 1f }; - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.JudgementStyle), nameof(AimErrorMeterStrings.JudgementStyleDescription))] - public Bindable JudgementStyle { get; } = new Bindable(); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitPositionStyle), nameof(AimErrorMeterStrings.HitPositionStyleDescription))] + public Bindable HitPositionStyle { get; } = new Bindable(); [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageSize), nameof(AimErrorMeterStrings.AverageSizeDescription))] public BindableNumber AverageSize { get; } = new BindableNumber(12f) @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Osu.HUD [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageStyle), nameof(AimErrorMeterStrings.AverageStyleDescription))] public Bindable AverageStyle { get; } = new Bindable(HitStyle.Plus); - [SettingSource("Position Style")] - public Bindable HitPositionStyle { get; } = new Bindable(); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.PositionStyle), nameof(AimErrorMeterStrings.PositionStyleDescription))] + public Bindable PositionMappingStyle { get; } = new Bindable(); [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; @@ -61,8 +61,8 @@ namespace osu.Game.Rulesets.Osu.HUD private Container averagePositionRotateContainer = null!; private Vector2 averagePosition; - private readonly DrawablePool hitPositionPool = new DrawablePool(20); - private Container hitPositionsContainer = null!; + private readonly DrawablePool hitPositionPool = new DrawablePool(30); + private Container hitPositionContainer = null!; private Container arrowBackgroundContainer = null!; private UprightAspectMaintainingContainer rotateFixedContainer = null!; @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.HUD Origin = Anchor.Centre, Children = new Drawable[] { - hitPositionsContainer = new Container + hitPositionContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -266,15 +266,15 @@ namespace osu.Game.Rulesets.Osu.HUD AverageSize.BindValueChanged(size => averagePositionContainer.Size = new Vector2(size.NewValue), true); AverageStyle.BindValueChanged(style => averagePositionRotateContainer.Rotation = style.NewValue == HitStyle.Plus ? 0 : 45, true); - HitPositionStyle.BindValueChanged(s => + PositionMappingStyle.BindValueChanged(s => { - foreach (var hit in hitPositionsContainer) + foreach (var hit in hitPositionContainer) { hit.FadeOut(300).Expire(); averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); } - if (s.NewValue == PositionStyle.Relative) + if (s.NewValue == MappingStyle.Relative) { arrowBackgroundContainer.FadeIn(100); rotateFixedContainer.Remove(mainContainer, false); @@ -295,12 +295,12 @@ namespace osu.Game.Rulesets.Osu.HUD if (circleJudgement.CursorPositionAtHit == null) return; - if (hitPositionsContainer.Count > max_concurrent_judgements) + if (hitPositionContainer.Count > max_concurrent_judgements) { const double quick_fade_time = 300; // check with a bit of lenience to avoid precision error in comparison. - var old = hitPositionsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); + var old = hitPositionContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); if (old != null) { @@ -311,7 +311,7 @@ namespace osu.Game.Rulesets.Osu.HUD Vector2 hitPosition; - if (HitPositionStyle.Value == PositionStyle.Relative && scoreProcessor.HitEvents.LastOrDefault().LastHitObject != null) + if (PositionMappingStyle.Value == MappingStyle.Relative && scoreProcessor.HitEvents.LastOrDefault().LastHitObject != null) { var currentHitEvent = scoreProcessor.HitEvents.Last(); @@ -329,7 +329,7 @@ namespace osu.Game.Rulesets.Osu.HUD drawableHit.Y = hitPosition.Y; drawableHit.Colour = getColourForPosition(hitPosition); - hitPositionsContainer.Add(drawableHit); + hitPositionContainer.Add(drawableHit); }); averagePositionContainer.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); @@ -380,7 +380,7 @@ namespace osu.Game.Rulesets.Osu.HUD { averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); - foreach (var h in hitPositionsContainer) + foreach (var h in hitPositionContainer) { h.ClearTransforms(); h.Expire(); @@ -392,9 +392,9 @@ namespace osu.Game.Rulesets.Osu.HUD [Resolved] private AimErrorMeter aimErrorMeter { get; set; } = null!; - public readonly BindableNumber JudgementSize = new BindableFloat(); + public readonly BindableNumber HitPointSize = new BindableFloat(); - public readonly Bindable JudgementStyle = new Bindable(); + public readonly Bindable HitPointStyle = new Bindable(); private readonly Container content; @@ -442,10 +442,10 @@ namespace osu.Game.Rulesets.Osu.HUD { base.LoadComplete(); - JudgementSize.BindTo(aimErrorMeter.JudgementSize); - JudgementSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); - JudgementStyle.BindTo(aimErrorMeter.JudgementStyle); - JudgementStyle.BindValueChanged(style => content.Rotation = style.NewValue == HitStyle.X ? 0 : 45, true); + HitPointSize.BindTo(aimErrorMeter.HitPositionSize); + HitPointSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); + HitPointStyle.BindTo(aimErrorMeter.HitPositionStyle); + HitPointStyle.BindValueChanged(style => content.Rotation = style.NewValue == HitStyle.X ? 0 : 45, true); } protected override void PrepareForUse() @@ -458,7 +458,7 @@ namespace osu.Game.Rulesets.Osu.HUD this .ResizeTo(new Vector2(0)) .FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint) - .ResizeTo(new Vector2(JudgementSize.Value), judgement_fade_in_duration, Easing.OutQuint) + .ResizeTo(new Vector2(HitPointSize.Value), judgement_fade_in_duration, Easing.OutQuint) .Then() .FadeOut(judgement_fade_out_duration) .Expire(); @@ -471,12 +471,15 @@ namespace osu.Game.Rulesets.Osu.HUD X, [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StylePlus))] - Plus + Plus, } - public enum PositionStyle + public enum MappingStyle { + [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Absolute))] Absolute, + + [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Relative))] Relative, } } diff --git a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs index b46e0ba380..c3db6e65a4 100644 --- a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs @@ -10,24 +10,24 @@ namespace osu.Game.Localisation.HUD private const string prefix = @"osu.Game.Resources.Localisation.HUD.AimErrorMeterStrings"; /// - /// "Judgement position size" + /// "Hit position size" /// - public static LocalisableString JudgementSize => new TranslatableString(getKey(@"judgement_size"), "Judgement position size"); + public static LocalisableString HitPositionSize => new TranslatableString(getKey(@"hit_position_size"), "Hit position size"); /// - /// "How big of judgement position should be." + /// "How big of hit position should be." /// - public static LocalisableString JudgementSizeDescription => new TranslatableString(getKey("judgement_size_description"), "How big of judgement position should be."); + public static LocalisableString HitPositionSizeDescription => new TranslatableString(getKey("hit_point_size_description"), "How big of hit position should be."); /// - /// "Judgement position style" + /// "Hit position style" /// - public static LocalisableString JudgementStyle => new TranslatableString(getKey(@"judgement_style"), "Judgement position style"); + public static LocalisableString HitPositionStyle => new TranslatableString(getKey(@"hit_position_style"), "Hit position style"); /// - /// "The style of judgement position." + /// "The style of hit position." /// - public static LocalisableString JudgementStyleDescription => new TranslatableString(getKey("judgement_style_description"), "The style of judgement position."); + public static LocalisableString HitPositionStyleDescription => new TranslatableString(getKey("hit_position_style_description"), "The style of hit position."); /// /// "Average position size" @@ -49,6 +49,16 @@ namespace osu.Game.Localisation.HUD /// public static LocalisableString AverageStyleDescription => new TranslatableString(getKey("average_style_description"), "The style of average position."); + /// + /// "Position mapping" + /// + public static LocalisableString PositionStyle => new TranslatableString(getKey("position_style"), "Position mapping"); + + /// + /// "Should hit point relative of last object" + /// + public static LocalisableString PositionStyleDescription => new TranslatableString(getKey("position_style_description"), "Should hit point relative of last object"); + /// /// "X" /// @@ -59,6 +69,16 @@ namespace osu.Game.Localisation.HUD /// public static LocalisableString StylePlus => new TranslatableString(getKey("style_plus"), "+"); + /// + /// "Absolute" + /// + public static LocalisableString Absolute => new TranslatableString(getKey("absolute"), "Absolute"); + + /// + /// "Relative" + /// + public static LocalisableString Relative => new TranslatableString(getKey("relative"), "Relative"); + private static string getKey(string key) => $"{prefix}:{key}"; } } From bf70552186519d7f49d08fa265f17a0e8699a6de Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 26 Jan 2024 14:32:52 +0900 Subject: [PATCH 24/38] add comment, fix some --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 40 ++++++---------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 81b22670e0..1df215a2bb 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -268,11 +268,8 @@ namespace osu.Game.Rulesets.Osu.HUD PositionMappingStyle.BindValueChanged(s => { - foreach (var hit in hitPositionContainer) - { - hit.FadeOut(300).Expire(); - averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); - } + // reset hit position to let it re-stat in the new mode + Clear(); if (s.NewValue == MappingStyle.Relative) { @@ -283,6 +280,8 @@ namespace osu.Game.Rulesets.Osu.HUD else { arrowBackgroundContainer.FadeOut(100); + // consider that component rotate is meaningless and will cause confusing in absolute mode. + // so let component in rotate fixed when in absolute mapping mode. RemoveInternal(mainContainer, false); rotateFixedContainer.Add(mainContainer); } @@ -309,20 +308,26 @@ namespace osu.Game.Rulesets.Osu.HUD } } + // the Vector2 for component is X (-0.5, 0.5), Y (-0.5, 0.5) Vector2 hitPosition; if (PositionMappingStyle.Value == MappingStyle.Relative && scoreProcessor.HitEvents.LastOrDefault().LastHitObject != null) { var currentHitEvent = scoreProcessor.HitEvents.Last(); + // let local center in (0.5, 0.5) to prevent localRadius in calculate will get zero. + // then manual subtraction 0.5 to match component mapping. hitPosition = AccuracyHeatmap.FindRelativeHitPosition(((OsuHitObject)currentHitEvent.LastHitObject).StackedEndPosition, ((OsuHitObject)currentHitEvent.HitObject).StackedEndPosition, circleJudgement.CursorPositionAtHit.Value, objectRadius, new Vector2(0.5f), inner_portion, 45) - new Vector2(0.5f); } else { - hitPosition = roundPosition((circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2 * inner_portion); + // get relative position between mouse position and current object. + hitPosition = (circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2 * inner_portion; } + hitPosition = Vector2.Clamp(hitPosition, new Vector2(-0.5f), new Vector2(0.5f)); + hitPositionPool.Get(drawableHit => { drawableHit.X = hitPosition.X; @@ -335,29 +340,6 @@ namespace osu.Game.Rulesets.Osu.HUD averagePositionContainer.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); } - private static Vector2 roundPosition(Vector2 position) - { - if (position.X > 0.5f) - { - position.X = 0.5f; - } - else if (position.X < -0.5f) - { - position.X = -0.5f; - } - - if (position.Y > 0.5f) - { - position.Y = 0.5f; - } - else if (position.Y < -0.5f) - { - position.Y = -0.5f; - } - - return position; - } - private Color4 getColourForPosition(Vector2 position) { switch (Vector2.Distance(position, Vector2.Zero)) From 0b5251fcf4da2be323ea154a3b85f665402bd30f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 20 Feb 2024 19:32:18 +0900 Subject: [PATCH 25/38] Remove dependencies on ScoreProcesser --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 1df215a2bb..24134c839d 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Osu.HUD [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.PositionStyle), nameof(AimErrorMeterStrings.PositionStyleDescription))] public Bindable PositionMappingStyle { get; } = new Bindable(); - [Resolved] - private ScoreProcessor scoreProcessor { get; set; } = null!; + // used for calculate relative position. + private Vector2? lastObjectPosition; private Container averagePositionContainer = null!; private Container averagePositionRotateContainer = null!; @@ -311,13 +311,11 @@ namespace osu.Game.Rulesets.Osu.HUD // the Vector2 for component is X (-0.5, 0.5), Y (-0.5, 0.5) Vector2 hitPosition; - if (PositionMappingStyle.Value == MappingStyle.Relative && scoreProcessor.HitEvents.LastOrDefault().LastHitObject != null) + if (PositionMappingStyle.Value == MappingStyle.Relative && lastObjectPosition != null) { - var currentHitEvent = scoreProcessor.HitEvents.Last(); - // let local center in (0.5, 0.5) to prevent localRadius in calculate will get zero. // then manual subtraction 0.5 to match component mapping. - hitPosition = AccuracyHeatmap.FindRelativeHitPosition(((OsuHitObject)currentHitEvent.LastHitObject).StackedEndPosition, ((OsuHitObject)currentHitEvent.HitObject).StackedEndPosition, + hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition, circleJudgement.CursorPositionAtHit.Value, objectRadius, new Vector2(0.5f), inner_portion, 45) - new Vector2(0.5f); } else @@ -338,6 +336,7 @@ namespace osu.Game.Rulesets.Osu.HUD }); averagePositionContainer.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); + lastObjectPosition = ((OsuHitObject)circleJudgement.HitObject).StackedPosition; } private Color4 getColourForPosition(Vector2 position) @@ -361,6 +360,7 @@ namespace osu.Game.Rulesets.Osu.HUD public override void Clear() { averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); + lastObjectPosition = null; foreach (var h in hitPositionContainer) { From 33ab00ecd81a0375797467d9a942bf77ec0aa0f2 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Mar 2024 13:15:29 +0900 Subject: [PATCH 26/38] code format --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 24134c839d..af687b59e7 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; From 398ac1b98d9ee153e6170e0a27944781394ab5a5 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 7 Jun 2024 20:05:27 +0900 Subject: [PATCH 27/38] improve testing can change aim meter style in test --- .../TestSceneAimErrorMeter.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index 080c60ecae..b6e4c43478 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Framework.Threading; using osu.Game.Rulesets.Osu.HUD; @@ -31,6 +32,22 @@ namespace osu.Game.Rulesets.Osu.Tests private ScheduledDelegate? automaticAdditionDelegate; + protected override void LoadComplete() + { + base.LoadComplete(); + + AddSliderStep("Hit position size", 0f, 12f, 7f, t => + { + if (aimErrorMeter.IsNotNull()) + aimErrorMeter.HitPositionSize.Value = t; + }); + AddSliderStep("Average position size", 1f, 25f, 7f, t => + { + if (aimErrorMeter.IsNotNull()) + aimErrorMeter.AverageSize.Value = t; + }); + } + [SetUpSteps] public void SetupSteps() => AddStep("Create components", () => { @@ -119,6 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("return user input", () => InputManager.UseParentInput = true); } + [Test] + public void TestDifferentStyle() + { + AddStep("Switch hit position style to +", () => aimErrorMeter.HitPositionStyle.Value = AimErrorMeter.HitStyle.Plus); + AddStep("Switch hit position style to x", () => aimErrorMeter.HitPositionStyle.Value = AimErrorMeter.HitStyle.X); + AddStep("Switch average position style to +", () => aimErrorMeter.AverageStyle.Value = AimErrorMeter.HitStyle.Plus); + AddStep("Switch average position style to x", () => aimErrorMeter.AverageStyle.Value = AimErrorMeter.HitStyle.X); + } + private partial class TestAimErrorMeter : AimErrorMeter { public void AddPoint(Vector2 position) From 20921577d72d5555666c4c5f103bee6101733cf2 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 10 Aug 2024 18:14:45 +0800 Subject: [PATCH 28/38] handle IApplicableToDifficulty for CS change. --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index af687b59e7..8bf9381907 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -15,10 +15,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Localisation.HUD; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; @@ -84,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.HUD } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, ScoreProcessor processor) { InternalChild = new Container { @@ -260,7 +262,21 @@ namespace osu.Game.Rulesets.Osu.HUD } }; - objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); + // handle IApplicableToDifficulty for CS change. + BeatmapDifficulty newDifficulty = new BeatmapDifficulty(); + beatmap.Value.Beatmap.Difficulty.CopyTo(newDifficulty); + + var mods = processor.Mods.Value; + + if (mods.Any(m => m is IApplicableToDifficulty)) + { + foreach (var mod in mods.OfType()) + { + mod.ApplyToDifficulty(newDifficulty); + } + } + + objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(newDifficulty.CircleSize, true); AverageSize.BindValueChanged(size => averagePositionContainer.Size = new Vector2(size.NewValue), true); AverageStyle.BindValueChanged(style => averagePositionRotateContainer.Rotation = style.NewValue == HitStyle.Plus ? 0 : 45, true); From 777ab611437f8895f52ef15ca0ef42ca4e3ca898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 12:46:53 +0200 Subject: [PATCH 29/38] Rename everything to start with --- .../TestSceneAimErrorMeter.cs | 16 ++-- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 89 ++++++++++--------- .../Localisation/HUD/AimErrorMeterStrings.cs | 58 +++++------- 3 files changed, 77 insertions(+), 86 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index b6e4c43478..b1ad2b2296 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -36,15 +36,15 @@ namespace osu.Game.Rulesets.Osu.Tests { base.LoadComplete(); - AddSliderStep("Hit position size", 0f, 12f, 7f, t => + AddSliderStep("Hit marker size", 0f, 12f, 7f, t => { if (aimErrorMeter.IsNotNull()) - aimErrorMeter.HitPositionSize.Value = t; + aimErrorMeter.HitMarkerSize.Value = t; }); - AddSliderStep("Average position size", 1f, 25f, 7f, t => + AddSliderStep("Average position marker size", 1f, 25f, 7f, t => { if (aimErrorMeter.IsNotNull()) - aimErrorMeter.AverageSize.Value = t; + aimErrorMeter.AverageMarkerSize.Value = t; }); } @@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestDifferentStyle() { - AddStep("Switch hit position style to +", () => aimErrorMeter.HitPositionStyle.Value = AimErrorMeter.HitStyle.Plus); - AddStep("Switch hit position style to x", () => aimErrorMeter.HitPositionStyle.Value = AimErrorMeter.HitStyle.X); - AddStep("Switch average position style to +", () => aimErrorMeter.AverageStyle.Value = AimErrorMeter.HitStyle.Plus); - AddStep("Switch average position style to x", () => aimErrorMeter.AverageStyle.Value = AimErrorMeter.HitStyle.X); + AddStep("Switch hit position marker style to +", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); + AddStep("Switch hit position marker style to x", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); + AddStep("Switch average position marker style to +", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); + AddStep("Switch average position marker style to x", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); } private partial class TestAimErrorMeter : AimErrorMeter diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 8bf9381907..0938bd31be 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,46 +25,47 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Rulesets.Osu.HUD { [Cached] public partial class AimErrorMeter : HitErrorMeter { - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitPositionSize), nameof(AimErrorMeterStrings.HitPositionSizeDescription))] - public BindableNumber HitPositionSize { get; } = new BindableNumber(7f) + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitMarkerSize), nameof(AimErrorMeterStrings.HitMarkerSizeDescription))] + public BindableNumber HitMarkerSize { get; } = new BindableNumber(7f) { MinValue = 0f, MaxValue = 12f, Precision = 1f }; - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitPositionStyle), nameof(AimErrorMeterStrings.HitPositionStyleDescription))] - public Bindable HitPositionStyle { get; } = new Bindable(); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitMarkerStyle), nameof(AimErrorMeterStrings.HitMarkerStyleDescription))] + public Bindable HitMarkerStyle { get; } = new Bindable(); - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageSize), nameof(AimErrorMeterStrings.AverageSizeDescription))] - public BindableNumber AverageSize { get; } = new BindableNumber(12f) + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageMarkerSize), nameof(AimErrorMeterStrings.AverageMarkerSizeDescription))] + public BindableNumber AverageMarkerSize { get; } = new BindableNumber(12f) { MinValue = 7f, MaxValue = 25f, Precision = 1f }; - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageStyle), nameof(AimErrorMeterStrings.AverageStyleDescription))] - public Bindable AverageStyle { get; } = new Bindable(HitStyle.Plus); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageMarkerStyle), nameof(AimErrorMeterStrings.AverageMarkerStyleDescription))] + public Bindable AverageMarkerStyle { get; } = new Bindable(MarkerStyle.Plus); - [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.PositionStyle), nameof(AimErrorMeterStrings.PositionStyleDescription))] - public Bindable PositionMappingStyle { get; } = new Bindable(); + [SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.PositionDisplayStyle), nameof(AimErrorMeterStrings.PositionDisplayStyleDescription))] + public Bindable PositionDisplayStyle { get; } = new Bindable(); // used for calculate relative position. private Vector2? lastObjectPosition; - private Container averagePositionContainer = null!; - private Container averagePositionRotateContainer = null!; + private Container averagePositionMarker = null!; + private Container averagePositionMarkerRotationContainer = null!; private Vector2 averagePosition; - private readonly DrawablePool hitPositionPool = new DrawablePool(30); - private Container hitPositionContainer = null!; + private readonly DrawablePool hitPositionPool = new DrawablePool(30); + private Container hitPositionMarkerContainer = null!; private Container arrowBackgroundContainer = null!; private UprightAspectMaintainingContainer rotateFixedContainer = null!; @@ -221,18 +223,18 @@ namespace osu.Game.Rulesets.Osu.HUD Origin = Anchor.Centre, Children = new Drawable[] { - hitPositionContainer = new Container + hitPositionMarkerContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre }, - averagePositionContainer = new UprightAspectMaintainingContainer + averagePositionMarker = new UprightAspectMaintainingContainer { RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = averagePositionRotateContainer = new Container + Child = averagePositionMarkerRotationContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -278,15 +280,15 @@ namespace osu.Game.Rulesets.Osu.HUD objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(newDifficulty.CircleSize, true); - AverageSize.BindValueChanged(size => averagePositionContainer.Size = new Vector2(size.NewValue), true); - AverageStyle.BindValueChanged(style => averagePositionRotateContainer.Rotation = style.NewValue == HitStyle.Plus ? 0 : 45, true); + AverageMarkerSize.BindValueChanged(size => averagePositionMarker.Size = new Vector2(size.NewValue), true); + AverageMarkerStyle.BindValueChanged(style => averagePositionMarkerRotationContainer.Rotation = style.NewValue == MarkerStyle.Plus ? 0 : 45, true); - PositionMappingStyle.BindValueChanged(s => + PositionDisplayStyle.BindValueChanged(s => { // reset hit position to let it re-stat in the new mode Clear(); - if (s.NewValue == MappingStyle.Relative) + if (s.NewValue == PositionDisplay.Normalised) { arrowBackgroundContainer.FadeIn(100); rotateFixedContainer.Remove(mainContainer, false); @@ -309,12 +311,12 @@ namespace osu.Game.Rulesets.Osu.HUD if (circleJudgement.CursorPositionAtHit == null) return; - if (hitPositionContainer.Count > max_concurrent_judgements) + if (hitPositionMarkerContainer.Count > max_concurrent_judgements) { const double quick_fade_time = 300; // check with a bit of lenience to avoid precision error in comparison. - var old = hitPositionContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); + var old = hitPositionMarkerContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); if (old != null) { @@ -326,7 +328,7 @@ namespace osu.Game.Rulesets.Osu.HUD // the Vector2 for component is X (-0.5, 0.5), Y (-0.5, 0.5) Vector2 hitPosition; - if (PositionMappingStyle.Value == MappingStyle.Relative && lastObjectPosition != null) + if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null) { // let local center in (0.5, 0.5) to prevent localRadius in calculate will get zero. // then manual subtraction 0.5 to match component mapping. @@ -347,10 +349,10 @@ namespace osu.Game.Rulesets.Osu.HUD drawableHit.Y = hitPosition.Y; drawableHit.Colour = getColourForPosition(hitPosition); - hitPositionContainer.Add(drawableHit); + hitPositionMarkerContainer.Add(drawableHit); }); - averagePositionContainer.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); + averagePositionMarker.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); lastObjectPosition = ((OsuHitObject)circleJudgement.HitObject).StackedPosition; } @@ -374,28 +376,27 @@ namespace osu.Game.Rulesets.Osu.HUD public override void Clear() { - averagePositionContainer.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); + averagePositionMarker.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); lastObjectPosition = null; - foreach (var h in hitPositionContainer) + foreach (var h in hitPositionMarkerContainer) { h.ClearTransforms(); h.Expire(); } } - private partial class HitPosition : PoolableDrawable + private partial class HitPositionMarker : PoolableDrawable { [Resolved] private AimErrorMeter aimErrorMeter { get; set; } = null!; - public readonly BindableNumber HitPointSize = new BindableFloat(); - - public readonly Bindable HitPointStyle = new Bindable(); + public readonly BindableNumber MarkerSize = new BindableFloat(); + public readonly Bindable Style = new Bindable(); private readonly Container content; - public HitPosition() + public HitPositionMarker() { RelativePositionAxes = Axes.Both; @@ -439,10 +440,10 @@ namespace osu.Game.Rulesets.Osu.HUD { base.LoadComplete(); - HitPointSize.BindTo(aimErrorMeter.HitPositionSize); - HitPointSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); - HitPointStyle.BindTo(aimErrorMeter.HitPositionStyle); - HitPointStyle.BindValueChanged(style => content.Rotation = style.NewValue == HitStyle.X ? 0 : 45, true); + MarkerSize.BindTo(aimErrorMeter.HitMarkerSize); + MarkerSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); + Style.BindTo(aimErrorMeter.HitMarkerStyle); + Style.BindValueChanged(style => content.Rotation = style.NewValue == AimErrorMeter.MarkerStyle.X ? 0 : 45, true); } protected override void PrepareForUse() @@ -455,29 +456,29 @@ namespace osu.Game.Rulesets.Osu.HUD this .ResizeTo(new Vector2(0)) .FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint) - .ResizeTo(new Vector2(HitPointSize.Value), judgement_fade_in_duration, Easing.OutQuint) + .ResizeTo(new Vector2(MarkerSize.Value), judgement_fade_in_duration, Easing.OutQuint) .Then() .FadeOut(judgement_fade_out_duration) .Expire(); } } - public enum HitStyle + public enum MarkerStyle { - [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StyleX))] + [Description("X")] X, - [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.StylePlus))] + [Description("+")] Plus, } - public enum MappingStyle + public enum PositionDisplay { [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Absolute))] Absolute, - [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Relative))] - Relative, + [LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Normalised))] + Normalised, } } } diff --git a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs index c3db6e65a4..31d81d41e3 100644 --- a/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs +++ b/osu.Game/Localisation/HUD/AimErrorMeterStrings.cs @@ -10,75 +10,65 @@ namespace osu.Game.Localisation.HUD private const string prefix = @"osu.Game.Resources.Localisation.HUD.AimErrorMeterStrings"; /// - /// "Hit position size" + /// "Hit marker size" /// - public static LocalisableString HitPositionSize => new TranslatableString(getKey(@"hit_position_size"), "Hit position size"); + public static LocalisableString HitMarkerSize => new TranslatableString(getKey(@"hit_marker_size"), @"Hit marker size"); /// - /// "How big of hit position should be." + /// "Controls the size of the markers displayed after every hit." /// - public static LocalisableString HitPositionSizeDescription => new TranslatableString(getKey("hit_point_size_description"), "How big of hit position should be."); + public static LocalisableString HitMarkerSizeDescription => new TranslatableString(getKey(@"hit_marker_size_description"), @"Controls the size of the markers displayed after every hit."); /// - /// "Hit position style" + /// "Hit marker style" /// - public static LocalisableString HitPositionStyle => new TranslatableString(getKey(@"hit_position_style"), "Hit position style"); + public static LocalisableString HitMarkerStyle => new TranslatableString(getKey(@"hit_marker_style"), @"Hit marker style"); /// - /// "The style of hit position." + /// "The visual style of the hit markers." /// - public static LocalisableString HitPositionStyleDescription => new TranslatableString(getKey("hit_position_style_description"), "The style of hit position."); + public static LocalisableString HitMarkerStyleDescription => new TranslatableString(getKey(@"hit_marker_style_description"), @"The visual style of the hit markers."); /// - /// "Average position size" + /// "Average position marker size" /// - public static LocalisableString AverageSize => new TranslatableString(getKey(@"average_size"), "Average position size"); + public static LocalisableString AverageMarkerSize => new TranslatableString(getKey(@"average_marker_size"), @"Average position marker size"); /// - /// "How big of average position should be." + /// "Controls the size of the marker showing average hit position." /// - public static LocalisableString AverageSizeDescription => new TranslatableString(getKey("average_size_description"), "How big of average position should be."); + public static LocalisableString AverageMarkerSizeDescription => new TranslatableString(getKey(@"average_marker_size_description"), @"Controls the size of the marker showing average hit position."); /// - /// "Average position style" + /// "Average position marker style" /// - public static LocalisableString AverageStyle => new TranslatableString(getKey(@"average_style"), "Average position style"); + public static LocalisableString AverageMarkerStyle => new TranslatableString(getKey(@"average_marker_style"), @"Average position marker style"); /// - /// "The style of average position." + /// "The visual style of the average position marker." /// - public static LocalisableString AverageStyleDescription => new TranslatableString(getKey("average_style_description"), "The style of average position."); + public static LocalisableString AverageMarkerStyleDescription => new TranslatableString(getKey(@"average_marker_style_description"), @"The visual style of the average position marker."); /// - /// "Position mapping" + /// "Position display style" /// - public static LocalisableString PositionStyle => new TranslatableString(getKey("position_style"), "Position mapping"); + public static LocalisableString PositionDisplayStyle => new TranslatableString(getKey(@"position_style"), @"Position display style"); /// - /// "Should hit point relative of last object" + /// "Controls whether positions displayed on the meter are absolute (as seen on screen) or normalised (relative to the direction of movement from previous object)." /// - public static LocalisableString PositionStyleDescription => new TranslatableString(getKey("position_style_description"), "Should hit point relative of last object"); - - /// - /// "X" - /// - public static LocalisableString StyleX => new TranslatableString(getKey("style_x"), "X"); - - /// - /// "+" - /// - public static LocalisableString StylePlus => new TranslatableString(getKey("style_plus"), "+"); + public static LocalisableString PositionDisplayStyleDescription => new TranslatableString(getKey(@"position_style_description"), @"Controls whether positions displayed on the meter are absolute (as seen on screen) or normalised (relative to the direction of movement from previous object)."); /// /// "Absolute" /// - public static LocalisableString Absolute => new TranslatableString(getKey("absolute"), "Absolute"); + public static LocalisableString Absolute => new TranslatableString(getKey(@"absolute"), @"Absolute"); /// - /// "Relative" + /// "Normalised" /// - public static LocalisableString Relative => new TranslatableString(getKey("relative"), "Relative"); + public static LocalisableString Normalised => new TranslatableString(getKey(@"normalised"), @"Normalised"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From d696ac99d43fb070de2d996360af9f8e0075b9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 13:20:38 +0200 Subject: [PATCH 30/38] Improve test coverage somewhat --- .../TestSceneAimErrorMeter.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index b1ad2b2296..eb7ba8f57e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -126,25 +126,27 @@ namespace osu.Game.Rulesets.Osu.Tests InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos)); }, 1, true); }); - AddWaitStep("wait for some hit points", 10); } + [Test] + public void TestDisplayStyles() + { + AddStep("Switch hit position marker style to +", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); + AddStep("Switch hit position marker style to x", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); + AddStep("Switch average position marker style to +", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); + AddStep("Switch average position marker style to x", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); + + AddStep("Switch position display to absolute", () => aimErrorMeter.PositionDisplayStyle.Value = AimErrorMeter.PositionDisplay.Absolute); + AddStep("Switch position display to relative", () => aimErrorMeter.PositionDisplayStyle.Value = AimErrorMeter.PositionDisplay.Normalised); + } + [Test] public void TestManualPlacement() { AddStep("return user input", () => InputManager.UseParentInput = true); } - [Test] - public void TestDifferentStyle() - { - AddStep("Switch hit position marker style to +", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); - AddStep("Switch hit position marker style to x", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); - AddStep("Switch average position marker style to +", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus); - AddStep("Switch average position marker style to x", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.X); - } - private partial class TestAimErrorMeter : AimErrorMeter { public void AddPoint(Vector2 position) From df210241fcdb439bdd8d21314a80487edc434bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 13:46:06 +0200 Subject: [PATCH 31/38] Fix manual click test being broken --- osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs index eb7ba8f57e..65ab6e7e15 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests gameObject = new CircularContainer { - Size = new Vector2(108), + Size = new Vector2(2 * OsuHitObject.OBJECT_RADIUS), Position = new Vector2(256, 192), Colour = Color4.Yellow, Masking = true, @@ -107,7 +107,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool OnMouseDown(MouseDownEvent e) { - aimErrorMeter.AddPoint(gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(54)); + // the division by 2 is because CS=5 applies a 0.5x (plus fudge) multiplier to `OBJECT_RADIUS` + aimErrorMeter.AddPoint((gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(OsuHitObject.OBJECT_RADIUS)) / 2); return true; } @@ -119,10 +120,10 @@ namespace osu.Game.Rulesets.Osu.Tests automaticAdditionDelegate = Scheduler.AddDelayed(() => { var randomPos = new Vector2( - RNG.NextSingle(0, 108), - RNG.NextSingle(0, 108)); + RNG.NextSingle(0, 2 * OsuHitObject.OBJECT_RADIUS), + RNG.NextSingle(0, 2 * OsuHitObject.OBJECT_RADIUS)); - aimErrorMeter.AddPoint(randomPos - new Vector2(54)); + aimErrorMeter.AddPoint(randomPos - new Vector2(OsuHitObject.OBJECT_RADIUS)); InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos)); }, 1, true); }); From 8dd349fd17df35be3a3615139241eafa0170f45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 13:54:18 +0200 Subject: [PATCH 32/38] Rewrite incomprehensible comments --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 0938bd31be..03c2888c10 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -285,7 +285,6 @@ namespace osu.Game.Rulesets.Osu.HUD PositionDisplayStyle.BindValueChanged(s => { - // reset hit position to let it re-stat in the new mode Clear(); if (s.NewValue == PositionDisplay.Normalised) @@ -297,8 +296,7 @@ namespace osu.Game.Rulesets.Osu.HUD else { arrowBackgroundContainer.FadeOut(100); - // consider that component rotate is meaningless and will cause confusing in absolute mode. - // so let component in rotate fixed when in absolute mapping mode. + // when in absolute mode, rotation of the aim error meter as a whole should not affect how the component is displayed RemoveInternal(mainContainer, false); rotateFixedContainer.Add(mainContainer); } @@ -325,7 +323,6 @@ namespace osu.Game.Rulesets.Osu.HUD } } - // the Vector2 for component is X (-0.5, 0.5), Y (-0.5, 0.5) Vector2 hitPosition; if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null) From b2dbd4a9dc3b32979cb432d6e6aa56df795e1ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 14:12:56 +0200 Subject: [PATCH 33/38] Make extracted helper more comprehensible --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 4 +-- .../Statistics/AccuracyHeatmap.cs | 36 ++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 03c2888c10..50f091ba79 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -327,10 +327,8 @@ namespace osu.Game.Rulesets.Osu.HUD if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null) { - // let local center in (0.5, 0.5) to prevent localRadius in calculate will get zero. - // then manual subtraction 0.5 to match component mapping. hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition, - circleJudgement.CursorPositionAtHit.Value, objectRadius, new Vector2(0.5f), inner_portion, 45) - new Vector2(0.5f); + circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * 0.5f; } else { diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index e31313f100..4a028d677a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -232,7 +232,11 @@ namespace osu.Game.Rulesets.Osu.Statistics if (pointGrid.Content.Count == 0) return; - Vector2 localPoint = FindRelativeHitPosition(start, end, hitPoint, radius, new Vector2(points_per_dimension - 1) / 2, inner_portion, rotation); + Vector2 relativePosition = FindRelativeHitPosition(start, end, hitPoint, radius, rotation); + + var localCentre = new Vector2(points_per_dimension - 1) / 2; + float localRadius = localCentre.X * inner_portion; + var localPoint = localCentre + localRadius * relativePosition; // Find the most relevant hit point. int r = (int)Math.Round(localPoint.Y); @@ -246,12 +250,29 @@ namespace osu.Game.Rulesets.Osu.Statistics bufferedGrid.ForceRedraw(); } - public static Vector2 FindRelativeHitPosition(Vector2 start, Vector2 end, Vector2 hitPoint, float radius, Vector2 localCentre, float innerPortion, float rotation) + /// + /// Normalises the position of a hit on a circle such that it is relative to the movement that was performed to arrive at said circle. + /// + /// The position of the object prior to the one getting hit. + /// The position of the object which is getting hit. + /// The point at which the user hit. + /// The radius of and . + /// + /// The rotation of the axis which is to be considered in the same direction as the vector + /// leading from to . + /// + /// + /// A 2D vector representing the as relative to the movement between and + /// and relative to the . + /// If the object was hit perfectly in the middle, the return value will be . + /// If the object was hit perfectly at its edge, the returned vector will have a magnitude of 1. + /// + public static Vector2 FindRelativeHitPosition(Vector2 previousObjectPosition, Vector2 nextObjectPosition, Vector2 hitPoint, float objectRadius, float rotation) { - double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. - double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. + double angle1 = Math.Atan2(nextObjectPosition.Y - hitPoint.Y, hitPoint.X - nextObjectPosition.X); // Angle between the end point and the hit point. + double angle2 = Math.Atan2(nextObjectPosition.Y - previousObjectPosition.Y, previousObjectPosition.X - nextObjectPosition.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; // Distance between the hit point and the end point. + float normalisedDistance = Vector2.Distance(hitPoint, nextObjectPosition) / objectRadius; // Distance between the hit point and the end point. // Consider two objects placed horizontally, with the start on the left and the end on the right. // The above calculated the angle between {end, start}, and the angle between {end, hitPoint}, in the form: @@ -270,10 +291,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // // We also need to apply the anti-clockwise rotation. double rotatedAngle = finalAngle - float.DegreesToRadians(rotation); - var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); - - float localRadius = localCentre.X * innerPortion * normalisedDistance; - return localCentre + localRadius * rotatedCoordinate; + return -normalisedDistance * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); } private abstract partial class GridPoint : CompositeDrawable From 49bb157fb887389b3f03980aa8053428884066bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 14:14:05 +0200 Subject: [PATCH 34/38] Remove undesirable switch syntax --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 50f091ba79..4fb46ae6d5 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -353,20 +353,18 @@ namespace osu.Game.Rulesets.Osu.HUD private Color4 getColourForPosition(Vector2 position) { - switch (Vector2.Distance(position, Vector2.Zero)) - { - case >= 0.5f * inner_portion: - return colours.Red; + float distance = Vector2.Distance(position, Vector2.Zero); - case >= 0.35f * inner_portion: - return colours.Yellow; + if (distance >= 0.5f * inner_portion) + return colours.Red; - case >= 0.2f * inner_portion: - return colours.Green; + if (distance >= 0.35f * inner_portion) + return colours.Yellow; - default: - return colours.Blue; - } + if (distance >= 0.2f * inner_portion) + return colours.Green; + + return colours.Blue; } public override void Clear() From fde288706842928767b1140bbf22919afff7719b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 14:40:07 +0200 Subject: [PATCH 35/38] Fix average marker not moving to first hit position --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 4fb46ae6d5..00877f1cd8 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.HUD private Container averagePositionMarker = null!; private Container averagePositionMarkerRotationContainer = null!; - private Vector2 averagePosition; + private Vector2? averagePosition; private readonly DrawablePool hitPositionPool = new DrawablePool(30); private Container hitPositionMarkerContainer = null!; @@ -347,7 +347,9 @@ namespace osu.Game.Rulesets.Osu.HUD hitPositionMarkerContainer.Add(drawableHit); }); - averagePositionMarker.MoveTo(averagePosition = (hitPosition + averagePosition) / 2, 800, Easing.OutQuint); + var newAveragePosition = (hitPosition + (averagePosition ?? hitPosition)) / 2; + averagePositionMarker.MoveTo(newAveragePosition, 800, Easing.OutQuint); + averagePosition = newAveragePosition; lastObjectPosition = ((OsuHitObject)circleJudgement.HitObject).StackedPosition; } @@ -369,7 +371,8 @@ namespace osu.Game.Rulesets.Osu.HUD public override void Clear() { - averagePositionMarker.MoveTo(averagePosition = Vector2.Zero, 800, Easing.OutQuint); + averagePosition = null; + averagePositionMarker.MoveTo(Vector2.Zero, 800, Easing.OutQuint); lastObjectPosition = null; foreach (var h in hitPositionMarkerContainer) From 807ba111fd3b01cec4cb717c3da405343d41c5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 14:40:50 +0200 Subject: [PATCH 36/38] Remove unnecessary condition --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index 00877f1cd8..d2b53e48cb 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -270,13 +270,8 @@ namespace osu.Game.Rulesets.Osu.HUD var mods = processor.Mods.Value; - if (mods.Any(m => m is IApplicableToDifficulty)) - { - foreach (var mod in mods.OfType()) - { - mod.ApplyToDifficulty(newDifficulty); - } - } + foreach (var mod in mods.OfType()) + mod.ApplyToDifficulty(newDifficulty); objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(newDifficulty.CircleSize, true); From a337c8bb99bd10ffa9cc21870597e4b2beb0a437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Aug 2025 14:43:24 +0200 Subject: [PATCH 37/38] Adjust weighted average to 90/10 to match bar error meter --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index d2b53e48cb..fb92770766 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.HUD hitPositionMarkerContainer.Add(drawableHit); }); - var newAveragePosition = (hitPosition + (averagePosition ?? hitPosition)) / 2; + var newAveragePosition = 0.1f * hitPosition + 0.9f * (averagePosition ?? hitPosition); averagePositionMarker.MoveTo(newAveragePosition, 800, Easing.OutQuint); averagePosition = newAveragePosition; lastObjectPosition = ((OsuHitObject)circleJudgement.HitObject).StackedPosition; @@ -434,7 +434,7 @@ namespace osu.Game.Rulesets.Osu.HUD MarkerSize.BindTo(aimErrorMeter.HitMarkerSize); MarkerSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true); Style.BindTo(aimErrorMeter.HitMarkerStyle); - Style.BindValueChanged(style => content.Rotation = style.NewValue == AimErrorMeter.MarkerStyle.X ? 0 : 45, true); + Style.BindValueChanged(style => content.Rotation = style.NewValue == MarkerStyle.X ? 0 : 45, true); } protected override void PrepareForUse() From ceb8a621ff6aa140186c9f6089aede2e5fe91f39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Aug 2025 16:43:55 +0900 Subject: [PATCH 38/38] Adjust marker style description to look more correct in dropdown --- osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs index fb92770766..8b3d505439 100644 --- a/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs +++ b/osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs @@ -456,7 +456,7 @@ namespace osu.Game.Rulesets.Osu.HUD public enum MarkerStyle { - [Description("X")] + [Description("x")] X, [Description("+")]