From b5dbf24d2787569b4f813bf5631507492cdb0269 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 1 Feb 2024 10:19:09 -0500 Subject: [PATCH 01/43] early replay analysis settings version committing early version for others in the discussion to do their own testing with it --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 + .../UI/HitMarkerContainer.cs | 134 ++++++++++++++++++ .../UI/OsuAnalysisSettings.cs | 81 +++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 + .../PlayerSettingsOverlayStrings.cs | 15 ++ osu.Game/Rulesets/Ruleset.cs | 3 + osu.Game/Screens/Play/Player.cs | 2 +- .../Play/PlayerSettings/AnalysisSettings.cs | 18 +++ osu.Game/Screens/Play/ReplayPlayer.cs | 5 + 9 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs create mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6752712be1a5..358553ac591d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -38,6 +38,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -356,5 +357,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } + + public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs new file mode 100644 index 000000000000..a9fc42e596ef --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -0,0 +1,134 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler + { + private Vector2 lastMousePosition; + + public Bindable HitMarkerEnabled = new BindableBool(); + public Bindable AimMarkersEnabled = new BindableBool(); + + public override bool ReceivePositionalInputAt(Vector2 _) => true; + + public bool OnPressed(KeyBindingPressEvent e) + { + if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) + { + AddMarker(e.Action); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) { } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + lastMousePosition = e.MousePosition; + + if (AimMarkersEnabled.Value) + { + AddMarker(null); + } + + return base.OnMouseMove(e); + } + + private void AddMarker(OsuAction? action) + { + Add(new HitMarkerDrawable(action) { Position = lastMousePosition }); + } + + private partial class HitMarkerDrawable : CompositeDrawable + { + private const double lifetime_duration = 1000; + private const double fade_out_time = 400; + + public override bool RemoveWhenNotAlive => true; + + public HitMarkerDrawable(OsuAction? action) + { + var colour = Colour4.Gray.Opacity(0.5F); + var length = 8; + var depth = float.MaxValue; + switch (action) + { + case OsuAction.LeftButton: + colour = Colour4.Orange; + length = 20; + depth = float.MinValue; + break; + case OsuAction.RightButton: + colour = Colour4.LightGreen; + length = 20; + depth = float.MinValue; + break; + } + + this.Depth = depth; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 45, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 135, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 45, + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 135, + Colour = colour + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + LifetimeStart = Time.Current; + LifetimeEnd = LifetimeStart + lifetime_duration; + + Scheduler.AddDelayed(() => + { + this.FadeOut(fade_out_time); + }, lifetime_duration - fade_out_time); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs new file mode 100644 index 000000000000..4694c1a5606a --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -0,0 +1,81 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.PlayerSettings; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class OsuAnalysisSettings : AnalysisSettings + { + private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings"); + + protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; + + private readonly PlayerCheckbox hitMarkerToggle; + private readonly PlayerCheckbox aimMarkerToggle; + private readonly PlayerCheckbox hideCursorToggle; + private readonly PlayerCheckbox? hiddenToggle; + + public OsuAnalysisSettings(DrawableRuleset drawableRuleset) + : base(drawableRuleset) + { + Children = new Drawable[] + { + hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, + aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } + }; + + // hidden stuff is just here for testing at the moment; to create the mod disabling functionality + + foreach (var mod in drawableRuleset.Mods) + { + if (mod is OsuModHidden) + { + logger.Add("Hidden is enabled", LogLevel.Debug); + Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" }); + break; + } + } + } + + protected override void LoadComplete() + { + drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + hideCursorToggle.Current.BindValueChanged(onCursorToggle); + hiddenToggle?.Current.BindValueChanged(onHiddenToggle); + } + + private void onCursorToggle(ValueChangedEvent hide) + { + // this only hides half the cursor + if (hide.NewValue) + { + drawableRuleset.Playfield.Cursor.Hide(); + } else + { + drawableRuleset.Playfield.Cursor.Show(); + } + } + + private void onHiddenToggle(ValueChangedEvent off) + { + if (off.NewValue) + { + logger.Add("Hidden off", LogLevel.Debug); + } else + { + logger.Add("Hidden on", LogLevel.Debug); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 411a02c5aff3..d7e173217545 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -36,6 +36,9 @@ public partial class OsuPlayfield : Playfield private readonly JudgementPooler judgementPooler; public SmokeContainer Smoke { get; } + + public HitMarkerContainer MarkersContainer { get; } + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -59,6 +62,7 @@ public OsuPlayfield() HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, + MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index 60874da561ff..f829fb4ac164 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -19,6 +19,21 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame"); + /// + /// "Hit markers" + /// + public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers"); + + /// + /// "Aim markers" + /// + public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers"); + + /// + /// "Hide cursor" + /// + public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 37a35fd3ae6c..5fbb23f094c6 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; @@ -402,5 +403,7 @@ protected Ruleset() /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; + + public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ad1f9ec8979e..0fa714369354 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -115,7 +115,7 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb public GameplayState GameplayState { get; private set; } - private Ruleset ruleset; + protected Ruleset ruleset; public BreakOverlay BreakOverlay; diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs new file mode 100644 index 000000000000..122ca291428d --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.UI; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public partial class AnalysisSettings : PlayerSettingsGroup + { + protected DrawableRuleset drawableRuleset; + + public AnalysisSettings(DrawableRuleset drawableRuleset) + : base("Analysis Settings") + { + this.drawableRuleset = drawableRuleset; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3c5b85662a3a..0d877785e7fc 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -71,6 +71,11 @@ private void load(OsuConfigManager config) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); + + var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) { + HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); + } } protected override void PrepareReplay() From 288eed53df439c594ffab544d3b83d9b2ea9d343 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Sat, 10 Feb 2024 02:02:26 -0500 Subject: [PATCH 02/43] new features + improvements Hit & aim markers are skinnable. Hidden can be toggled off. Aim line with skinnable color was added. The fadeout time is based on the approach rate. Cursor hide fixed. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 77 +++++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 + .../Skinning/Default/DefaultHitMarker.cs | 71 +++++++ .../Skinning/Default/HitMarker.cs | 33 ++++ .../Legacy/OsuLegacySkinTransformer.cs | 19 ++ .../Skinning/OsuSkinColour.cs | 1 + .../UI/HitMarkerContainer.cs | 182 ++++++++++++------ .../UI/OsuAnalysisSettings.cs | 68 +++++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../PlayerSettingsOverlayStrings.cs | 5 + .../Rulesets/Mods/IToggleableVisibility.cs | 11 ++ 12 files changed, 390 insertions(+), 94 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs create mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 6dc0d5d5229f..a69bed6fb25a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -12,13 +14,14 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IHidesApproachCircles + public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility { [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); @@ -28,24 +31,41 @@ public class OsuModHidden : ModHidden, IHidesApproachCircles public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; + private bool toggledOff = false; + private IBeatmap? appliedBeatmap; + public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); - public override void ApplyToBeatmap(IBeatmap beatmap) + public override void ApplyToBeatmap(IBeatmap? beatmap = null) { + if (beatmap is not null) + appliedBeatmap = beatmap; + else if (appliedBeatmap is null) + return; + else + beatmap = appliedBeatmap; + base.ApplyToBeatmap(beatmap); foreach (var obj in beatmap.HitObjects.OfType()) applyFadeInAdjustment(obj); + } - static void applyFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; - foreach (var nested in osuObject.NestedHitObjects.OfType()) - applyFadeInAdjustment(nested); - } + private static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } + + private static void revertFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt); + foreach (var nested in osuObject.NestedHitObjects.OfType()) + revertFadeInAdjustment(nested); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -60,7 +80,7 @@ protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -183,8 +203,11 @@ private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVis } } - private static void hideSpinnerApproachCircle(DrawableSpinner spinner) + private void hideSpinnerApproachCircle(DrawableSpinner spinner) { + if (toggledOff) + return; + var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; @@ -192,5 +215,39 @@ private static void hideSpinnerApproachCircle(DrawableSpinner spinner) using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) approachCircle.Hide(); } + + public void ToggleOffVisibility(Playfield playfield) + { + if (toggledOff) + return; + + toggledOff = true; + + if (appliedBeatmap is not null) + foreach (var obj in appliedBeatmap.HitObjects.OfType()) + revertFadeInAdjustment(obj); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + } + } + + public void ToggleOnVisibility(Playfield playfield) + { + if (!toggledOff) + return; + + toggledOff = false; + + if (appliedBeatmap is not null) + ApplyToBeatmap(); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + ApplyToDrawableHitObject(dho); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 74631400cae9..60305ed95378 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -154,17 +154,19 @@ protected OsuHitObject() }); } + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); - // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. - // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. - // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. - // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. - TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); + TimeFadeIn = CalculateTimeFadeIn(TimePreempt); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 52fdfea95f96..75db18d345e9 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -22,5 +22,8 @@ public enum OsuSkinComponents SpinnerBody, CursorSmoke, ApproachCircle, + HitMarkerLeft, + HitMarkerRight, + AimMarker } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs new file mode 100644 index 000000000000..f4c11e6c8a08 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -0,0 +1,71 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class DefaultHitMarker : CompositeDrawable + { + public DefaultHitMarker(OsuAction? action) + { + var (colour, length, hasBorder) = getConfig(action); + + if (hasBorder) + { + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 45, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 135, + Colour = Colour4.Black.Opacity(0.5F) + } + }; + } + + AddRangeInternal(new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 45, + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 135, + Colour = colour + } + }); + } + + private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) + { + switch (action) + { + case OsuAction.LeftButton: + return (Colour4.Orange, 20, true); + case OsuAction.RightButton: + return (Colour4.LightGreen, 20, true); + default: + return (Colour4.Gray.Opacity(0.3F), 8, false); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs new file mode 100644 index 000000000000..d86e3d4f79a4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -0,0 +1,33 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class HitMarker : Sprite + { + private readonly OsuAction? action; + + public HitMarker(OsuAction? action = null) + { + this.action = action; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + switch (action) + { + case OsuAction.LeftButton: + Texture = skin.GetTexture(@"hitmarker-left"); + break; + case OsuAction.RightButton: + Texture = skin.GetTexture(@"hitmarker-right"); + break; + default: + Texture = skin.GetTexture(@"aimmarker"); + break; + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d2ebc68c5225..84c055fe5e50 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -168,6 +169,24 @@ public OsuLegacySkinTransformer(ISkin skin) return null; + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; + default: throw new UnsupportedSkinComponentException(lookup); } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 24f9217a5f45..5c864fb6c21a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -10,5 +10,6 @@ public enum OsuSkinColour SliderBall, SpinnerBackground, StarBreakAdditive, + ReplayAimLine, } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs index a9fc42e596ef..01877a91851a 100644 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -10,124 +10,186 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI { public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { private Vector2 lastMousePosition; + private Vector2? lastlastMousePosition; + private double? timePreempt; + private double TimePreempt + { + get => timePreempt ?? default_time_preempt; + set => timePreempt = value; + } public Bindable HitMarkerEnabled = new BindableBool(); public Bindable AimMarkersEnabled = new BindableBool(); + public Bindable AimLinesEnabled = new BindableBool(); + + private const double default_time_preempt = 1000; + + private readonly HitObjectContainer hitObjectContainer; public override bool ReceivePositionalInputAt(Vector2 _) => true; + public HitMarkerContainer(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + } + public bool OnPressed(KeyBindingPressEvent e) { if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) { + updateTimePreempt(); AddMarker(e.Action); } return false; } - public void OnReleased(KeyBindingReleaseEvent e) { } + public void OnReleased(KeyBindingReleaseEvent e) + { + } protected override bool OnMouseMove(MouseMoveEvent e) { + lastlastMousePosition = lastMousePosition; lastMousePosition = e.MousePosition; if (AimMarkersEnabled.Value) { + updateTimePreempt(); AddMarker(null); } + if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) + { + if (!AimMarkersEnabled.Value) + updateTimePreempt(); + Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt)); + } + return base.OnMouseMove(e); } private void AddMarker(OsuAction? action) { - Add(new HitMarkerDrawable(action) { Position = lastMousePosition }); + var component = OsuSkinComponents.AimMarker; + switch(action) + { + case OsuAction.LeftButton: + component = OsuSkinComponents.HitMarkerLeft; + break; + case OsuAction.RightButton: + component = OsuSkinComponents.HitMarkerRight; + break; + } + + Add(new HitMarkerDrawable(action, component, TimePreempt) + { + Position = lastMousePosition, + Origin = Anchor.Centre, + Depth = action == null ? float.MaxValue : float.MinValue + }); + } + + private void updateTimePreempt() + { + var hitObject = getHitObject(); + if (hitObject == null) + return; + + TimePreempt = hitObject.TimePreempt; + } + + private OsuHitObject? getHitObject() + { + foreach (var dho in hitObjectContainer.Objects) + return (dho as DrawableOsuHitObject)?.HitObject; + return null; } - private partial class HitMarkerDrawable : CompositeDrawable + private partial class HitMarkerDrawable : SkinnableDrawable { - private const double lifetime_duration = 1000; - private const double fade_out_time = 400; + private readonly double lifetimeDuration; + private readonly double fadeOutTime; public override bool RemoveWhenNotAlive => true; - public HitMarkerDrawable(OsuAction? action) + public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt) + : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action)) + { + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; + } + + protected override void LoadComplete() { - var colour = Colour4.Gray.Opacity(0.5F); - var length = 8; - var depth = float.MaxValue; - switch (action) + base.LoadComplete(); + + LifetimeStart = Time.Current; + LifetimeEnd = LifetimeStart + lifetimeDuration; + + Scheduler.AddDelayed(() => { - case OsuAction.LeftButton: - colour = Colour4.Orange; - length = 20; - depth = float.MinValue; - break; - case OsuAction.RightButton: - colour = Colour4.LightGreen; - length = 20; - depth = float.MinValue; - break; - } - - this.Depth = depth; - - InternalChildren = new Drawable[] + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); + } + } + + private partial class AimLineDrawable : CompositeDrawable + { + private readonly double lifetimeDuration; + private readonly double fadeOutTime; + + public override bool RemoveWhenNotAlive => true; + + public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt) + { + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; + + float distance = Vector2.Distance(fromP, toP); + Vector2 direction = (toP - fromP); + InternalChild = new Box { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 45, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 135, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 45, - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 135, - Colour = colour - } + Position = fromP + (direction / 2), + Size = new Vector2(distance, 1), + Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)), + Origin = Anchor.Centre }; } + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; + color.A = 127; + InternalChild.Colour = color; + } + protected override void LoadComplete() { base.LoadComplete(); LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetime_duration; + LifetimeEnd = LifetimeStart + lifetimeDuration; Scheduler.AddDelayed(() => { - this.FadeOut(fade_out_time); - }, lifetime_duration - fade_out_time); + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 4694c1a5606a..050675d97086 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -4,7 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -15,14 +17,13 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings"); - protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; private readonly PlayerCheckbox hideCursorToggle; - private readonly PlayerCheckbox? hiddenToggle; + private readonly PlayerCheckbox aimLinesToggle; + private readonly FillFlowContainer modTogglesContainer; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) @@ -31,18 +32,36 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) { hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } + aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = ModSwitchSmall.DEFAULT_SIZE, + ScrollbarOverlapsContent = false, + Child = modTogglesContainer = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + } + } }; - - // hidden stuff is just here for testing at the moment; to create the mod disabling functionality foreach (var mod in drawableRuleset.Mods) { - if (mod is OsuModHidden) + if (mod is IToggleableVisibility toggleableMod) { - logger.Add("Hidden is enabled", LogLevel.Debug); - Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" }); - break; + var modSwitch = new SelectableModSwitchSmall(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { Value = true } + }; + modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v)); + modTogglesContainer.Add(modSwitch); } } } @@ -51,8 +70,8 @@ protected override void LoadComplete() { drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); - hiddenToggle?.Current.BindValueChanged(onHiddenToggle); } private void onCursorToggle(ValueChangedEvent hide) @@ -60,21 +79,34 @@ private void onCursorToggle(ValueChangedEvent hide) // this only hides half the cursor if (hide.NewValue) { - drawableRuleset.Playfield.Cursor.Hide(); + drawableRuleset.Playfield.Cursor.FadeOut(); } else { - drawableRuleset.Playfield.Cursor.Show(); + drawableRuleset.Playfield.Cursor.FadeIn(); } } - private void onHiddenToggle(ValueChangedEvent off) + private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled) { - if (off.NewValue) + if (toggled.NewValue) { - logger.Add("Hidden off", LogLevel.Debug); + mod.ToggleOnVisibility(drawableRuleset.Playfield); } else { - logger.Add("Hidden on", LogLevel.Debug); + mod.ToggleOffVisibility(drawableRuleset.Playfield); + } + } + + private partial class SelectableModSwitchSmall : ModSwitchSmall + { + public SelectableModSwitchSmall(IMod mod) + : base(mod) + {} + + protected override bool OnClick(ClickEvent e) + { + Active.Value = !Active.Value; + return true; } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d7e173217545..3cb3c50ef722 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -62,7 +62,7 @@ public OsuPlayfield() HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both } + MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index f829fb4ac164..017cc9bf82c4 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -34,6 +34,11 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); + /// + /// "Aim lines" + /// + public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs new file mode 100644 index 000000000000..5d90c24b9e35 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs @@ -0,0 +1,11 @@ +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public interface IToggleableVisibility + { + public void ToggleOffVisibility(Playfield playfield); + + public void ToggleOnVisibility(Playfield playfield); + } +} \ No newline at end of file From 1d552e7c90b8d3f7350fecaa43f5759b0154eaa7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 20 Feb 2024 22:28:25 -0500 Subject: [PATCH 03/43] move skin component logic --- .../Default/OsuTrianglesSkinTransformer.cs | 22 +++++++++++++++++++ .../Legacy/OsuLegacySkinTransformer.cs | 22 ------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs index 7a4c768aa25f..69ef1d9dc095 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -29,6 +29,28 @@ public OsuTrianglesSkinTransformer(ISkin skin) return new DefaultJudgementPieceSliderTickMiss(result); } + break; + case OsuSkinComponentLookup osuComponent: + switch (osuComponent.Component) + { + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; + } break; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 84c055fe5e50..a931d3ecbf32 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -168,27 +167,6 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; - - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; - - default: - throw new UnsupportedSkinComponentException(lookup); } } From 35b89966bccc2455034c7aed973cf2fefbf87ca7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Wed, 21 Feb 2024 23:23:40 -0500 Subject: [PATCH 04/43] revert mod visibility toggle --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 79 +++---------------- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 ++- .../UI/OsuAnalysisSettings.cs | 57 +------------ .../Rulesets/Mods/IToggleableVisibility.cs | 11 --- 4 files changed, 17 insertions(+), 142 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index a69bed6fb25a..e45daed9199c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -14,14 +12,13 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility + public class OsuModHidden : ModHidden, IHidesApproachCircles { [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); @@ -31,41 +28,24 @@ public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibil public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; - private bool toggledOff = false; - private IBeatmap? appliedBeatmap; - public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); - public override void ApplyToBeatmap(IBeatmap? beatmap = null) + public override void ApplyToBeatmap(IBeatmap beatmap) { - if (beatmap is not null) - appliedBeatmap = beatmap; - else if (appliedBeatmap is null) - return; - else - beatmap = appliedBeatmap; - base.ApplyToBeatmap(beatmap); foreach (var obj in beatmap.HitObjects.OfType()) applyFadeInAdjustment(obj); - } - private static void applyFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; - foreach (var nested in osuObject.NestedHitObjects.OfType()) - applyFadeInAdjustment(nested); - } - - private static void revertFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt); - foreach (var nested in osuObject.NestedHitObjects.OfType()) - revertFadeInAdjustment(nested); + static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -80,7 +60,7 @@ protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -203,11 +183,8 @@ private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVis } } - private void hideSpinnerApproachCircle(DrawableSpinner spinner) + private static void hideSpinnerApproachCircle(DrawableSpinner spinner) { - if (toggledOff) - return; - var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; @@ -215,39 +192,5 @@ private void hideSpinnerApproachCircle(DrawableSpinner spinner) using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) approachCircle.Hide(); } - - public void ToggleOffVisibility(Playfield playfield) - { - if (toggledOff) - return; - - toggledOff = true; - - if (appliedBeatmap is not null) - foreach (var obj in appliedBeatmap.HitObjects.OfType()) - revertFadeInAdjustment(obj); - - foreach (var dho in playfield.AllHitObjects) - { - dho.RefreshStateTransforms(); - } - } - - public void ToggleOnVisibility(Playfield playfield) - { - if (!toggledOff) - return; - - toggledOff = false; - - if (appliedBeatmap is not null) - ApplyToBeatmap(); - - foreach (var dho in playfield.AllHitObjects) - { - dho.RefreshStateTransforms(); - ApplyToDrawableHitObject(dho); - } - } } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 60305ed95378..74631400cae9 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -154,19 +154,17 @@ protected OsuHitObject() }); } - // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. - // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. - // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. - // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. - static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN); - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); - TimeFadeIn = CalculateTimeFadeIn(TimePreempt); + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 050675d97086..319ae3e1f5a4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -8,7 +8,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Localisation; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -23,7 +22,6 @@ public partial class OsuAnalysisSettings : AnalysisSettings private readonly PlayerCheckbox aimMarkerToggle; private readonly PlayerCheckbox hideCursorToggle; private readonly PlayerCheckbox aimLinesToggle; - private readonly FillFlowContainer modTogglesContainer; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) @@ -33,37 +31,8 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.X, - Height = ModSwitchSmall.DEFAULT_SIZE, - ScrollbarOverlapsContent = false, - Child = modTogglesContainer = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X - } - } + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } }; - - foreach (var mod in drawableRuleset.Mods) - { - if (mod is IToggleableVisibility toggleableMod) - { - var modSwitch = new SelectableModSwitchSmall(mod) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Active = { Value = true } - }; - modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v)); - modTogglesContainer.Add(modSwitch); - } - } } protected override void LoadComplete() @@ -85,29 +54,5 @@ private void onCursorToggle(ValueChangedEvent hide) drawableRuleset.Playfield.Cursor.FadeIn(); } } - - private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled) - { - if (toggled.NewValue) - { - mod.ToggleOnVisibility(drawableRuleset.Playfield); - } else - { - mod.ToggleOffVisibility(drawableRuleset.Playfield); - } - } - - private partial class SelectableModSwitchSmall : ModSwitchSmall - { - public SelectableModSwitchSmall(IMod mod) - : base(mod) - {} - - protected override bool OnClick(ClickEvent e) - { - Active.Value = !Active.Value; - return true; - } - } } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs deleted file mode 100644 index 5d90c24b9e35..000000000000 --- a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs +++ /dev/null @@ -1,11 +0,0 @@ -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mods -{ - public interface IToggleableVisibility - { - public void ToggleOffVisibility(Playfield playfield); - - public void ToggleOnVisibility(Playfield playfield); - } -} \ No newline at end of file From f9d9df30b2104322920eb8326ad01ea946f87f06 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 07:31:15 -0500 Subject: [PATCH 05/43] add test for HitMarkerContainer --- .../Resources/special-skin/aimmarker@2x.png | Bin 0 -> 648 bytes .../special-skin/hitmarker-left@2x.png | Bin 0 -> 2127 bytes .../special-skin/hitmarker-right@2x.png | Bin 0 -> 1742 bytes .../Resources/special-skin/skin.ini | 5 +- .../TestSceneHitMarker.cs | 134 ++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2a554193f08f7984568980956a3db8a6b39800 GIT binary patch literal 648 zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@ z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*= zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC> zq=ruJv2<)!2?D2qeSqx&#}JNi2t zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_ z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq? zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|# zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_ z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE% zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1? zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O! zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6 zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8* zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m&#*Br_F z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y- z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk FV1jr`?veli literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f GIT binary patch literal 1742 zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw? zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub! zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini index 9d16267d733b..2952948f452a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini @@ -1,4 +1,7 @@ [General] Version: latest HitCircleOverlayAboveNumber: 0 -HitCirclePrefix: display \ No newline at end of file +HitCirclePrefix: display + +[Colours] +ReplayAimLine: 0,0,255 \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs new file mode 100644 index 000000000000..a0748e31af8d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -0,0 +1,134 @@ +// 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.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Framework.Logging; +using osu.Framework.Testing.Input; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneHitMarker : OsuSkinnableTestScene + { + [Test] + public void TestHitMarkers() + { + var markerContainers = new List(); + + AddStep("Create hit markers", () => + { + markerContainers.Clear(); + SetContents(_ => { + markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) + { + HitMarkerEnabled = { Value = true }, + AimMarkersEnabled = { Value = true }, + AimLinesEnabled = { Value = true }, + RelativeSizeAxes = Axes.Both + }); + + return new HitMarkerInputManager + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.95f), + Child = markerContainers[^1], + }; + }); + }); + + AddUntilStep("Until skinnable expires", () => + { + if (markerContainers.Count == 0) + return false; + + Logger.Log("How many: " + markerContainers.Count); + + foreach (var markerContainer in markerContainers) + { + if (markerContainer.Children.Count != 0) + return false; + } + + return true; + }); + } + + private partial class HitMarkerInputManager : ManualInputManager + { + private double? startTime; + + public HitMarkerInputManager() + { + UseParentInput = false; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MoveMouseTo(ToScreenSpace(DrawSize / 2)); + } + + protected override void Update() + { + base.Update(); + + const float spin_angle = 4 * MathF.PI; + + startTime ??= Time.Current; + + float fraction = (float)((Time.Current - startTime) / 5_000); + + float angle = fraction * spin_angle; + float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; + + Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2; + MoveMouseTo(ToScreenSpace(pos)); + } + } + + private partial class TestHitMarkerContainer : HitMarkerContainer + { + private double? lastClick; + private double? startTime; + private bool finishedDrawing = false; + private bool leftOrRight = false; + + public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + protected override void Update() + { + base.Update(); + + if (finishedDrawing) + return; + + startTime ??= lastClick ??= Time.Current; + + if (startTime + 5_000 <= Time.Current) + { + finishedDrawing = true; + HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false; + return; + } + + if (lastClick + 400 <= Time.Current) + { + OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton)); + leftOrRight = !leftOrRight; + lastClick = Time.Current; + } + } + } + } +} \ No newline at end of file From af13389a9e8fc58ef549cb8e9920375c22dc99ef Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 07:31:56 -0500 Subject: [PATCH 06/43] fix hit marker skinnables --- .../Default/OsuTrianglesSkinTransformer.cs | 22 ------------------- .../Legacy/OsuLegacySkinTransformer.cs | 19 ++++++++++++++++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs index 69ef1d9dc095..7a4c768aa25f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -29,28 +29,6 @@ public OsuTrianglesSkinTransformer(ISkin skin) return new DefaultJudgementPieceSliderTickMiss(result); } - break; - case OsuSkinComponentLookup osuComponent: - switch (osuComponent.Component) - { - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; - } break; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index a931d3ecbf32..097f1732e2a0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -167,6 +168,24 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; + + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; } } From 45444b39d461ca9c497cc7ffdac8402b98e26e03 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 19:01:52 -0500 Subject: [PATCH 07/43] fix formatting issues --- .../TestSceneHitMarker.cs | 11 ++++--- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Skinning/Default/DefaultHitMarker.cs | 7 ++++- .../Skinning/Default/HitMarker.cs | 7 ++++- .../Legacy/OsuLegacySkinTransformer.cs | 4 +-- .../UI/HitMarkerContainer.cs | 31 +++++++++---------- .../UI/OsuAnalysisSettings.cs | 22 ++++++------- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- .../Play/PlayerSettings/AnalysisSettings.cs | 6 ++-- osu.Game/Screens/Play/ReplayPlayer.cs | 5 ++- 12 files changed, 53 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs index a0748e31af8d..7ba681b50f1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -25,8 +25,9 @@ public void TestHitMarkers() AddStep("Create hit markers", () => { markerContainers.Clear(); - SetContents(_ => { - markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) + SetContents(_ => + { + markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) { HitMarkerEnabled = { Value = true }, AimMarkersEnabled = { Value = true }, @@ -98,8 +99,8 @@ private partial class TestHitMarkerContainer : HitMarkerContainer { private double? lastClick; private double? startTime; - private bool finishedDrawing = false; - private bool leftOrRight = false; + private bool finishedDrawing; + private bool leftOrRight; public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) : base(hitObjectContainer) @@ -131,4 +132,4 @@ protected override void Update() } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index e45daed9199c..6dc0d5d5229f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -193,4 +193,4 @@ private static void hideSpinnerApproachCircle(DrawableSpinner spinner) approachCircle.Hide(); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 358553ac591d..224d08cbd505 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -358,6 +358,6 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); + public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs index f4c11e6c8a08..7dabb5182f70 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -1,3 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -61,11 +64,13 @@ public DefaultHitMarker(OsuAction? action) { case OsuAction.LeftButton: return (Colour4.Orange, 20, true); + case OsuAction.RightButton: return (Colour4.LightGreen, 20, true); + default: return (Colour4.Gray.Opacity(0.3F), 8, false); } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs index d86e3d4f79a4..28877345d0b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -1,3 +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 osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; @@ -21,13 +24,15 @@ private void load(ISkinSource skin) case OsuAction.LeftButton: Texture = skin.GetTexture(@"hitmarker-left"); break; + case OsuAction.RightButton: Texture = skin.GetTexture(@"hitmarker-right"); break; + default: Texture = skin.GetTexture(@"aimmarker"); break; } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 097f1732e2a0..61f9eebd862f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -168,7 +168,7 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; - + case OsuSkinComponents.HitMarkerLeft: if (GetTexture(@"hitmarker-left") != null) return new HitMarker(OsuAction.LeftButton); @@ -184,7 +184,7 @@ public OsuLegacySkinTransformer(ISkin skin) case OsuSkinComponents.AimMarker: if (GetTexture(@"aimmarker") != null) return new HitMarker(); - + return null; } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs index 01877a91851a..e8916ea5451a 100644 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -25,12 +25,7 @@ public partial class HitMarkerContainer : Container, IRequireHighFrequencyMouseP { private Vector2 lastMousePosition; private Vector2? lastlastMousePosition; - private double? timePreempt; - private double TimePreempt - { - get => timePreempt ?? default_time_preempt; - set => timePreempt = value; - } + private double timePreempt; public Bindable HitMarkerEnabled = new BindableBool(); public Bindable AimMarkersEnabled = new BindableBool(); @@ -45,6 +40,7 @@ private double TimePreempt public HitMarkerContainer(HitObjectContainer hitObjectContainer) { this.hitObjectContainer = hitObjectContainer; + timePreempt = default_time_preempt; } public bool OnPressed(KeyBindingPressEvent e) @@ -52,7 +48,7 @@ public bool OnPressed(KeyBindingPressEvent e) if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) { updateTimePreempt(); - AddMarker(e.Action); + addMarker(e.Action); } return false; @@ -70,33 +66,35 @@ protected override bool OnMouseMove(MouseMoveEvent e) if (AimMarkersEnabled.Value) { updateTimePreempt(); - AddMarker(null); + addMarker(null); } if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) { if (!AimMarkersEnabled.Value) updateTimePreempt(); - Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt)); + Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt)); } return base.OnMouseMove(e); } - private void AddMarker(OsuAction? action) + private void addMarker(OsuAction? action) { var component = OsuSkinComponents.AimMarker; - switch(action) + + switch (action) { case OsuAction.LeftButton: component = OsuSkinComponents.HitMarkerLeft; break; + case OsuAction.RightButton: component = OsuSkinComponents.HitMarkerRight; break; } - Add(new HitMarkerDrawable(action, component, TimePreempt) + Add(new HitMarkerDrawable(action, component, timePreempt) { Position = lastMousePosition, Origin = Anchor.Centre, @@ -110,13 +108,14 @@ private void updateTimePreempt() if (hitObject == null) return; - TimePreempt = hitObject.TimePreempt; + timePreempt = hitObject.TimePreempt; } private OsuHitObject? getHitObject() { foreach (var dho in hitObjectContainer.Objects) return (dho as DrawableOsuHitObject)?.HitObject; + return null; } @@ -141,7 +140,7 @@ protected override void LoadComplete() LifetimeStart = Time.Current; LifetimeEnd = LifetimeStart + lifetimeDuration; - Scheduler.AddDelayed(() => + Scheduler.AddDelayed(() => { this.FadeOut(fadeOutTime); }, lifetimeDuration - fadeOutTime); @@ -186,11 +185,11 @@ protected override void LoadComplete() LifetimeStart = Time.Current; LifetimeEnd = LifetimeStart + lifetimeDuration; - Scheduler.AddDelayed(() => + Scheduler.AddDelayed(() => { this.FadeOut(fadeOutTime); }, lifetimeDuration - fadeOutTime); } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 319ae3e1f5a4..51fd835dc502 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -1,14 +1,9 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Localisation; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -16,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; + protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; @@ -37,9 +32,9 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) protected override void LoadComplete() { - drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); } @@ -48,11 +43,12 @@ private void onCursorToggle(ValueChangedEvent hide) // this only hides half the cursor if (hide.NewValue) { - drawableRuleset.Playfield.Cursor.FadeOut(); - } else + DrawableRuleset.Playfield.Cursor.FadeOut(); + } + else { - drawableRuleset.Playfield.Cursor.FadeIn(); + DrawableRuleset.Playfield.Cursor.FadeIn(); } } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 5fbb23f094c6..84177f26b07d 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -403,7 +403,7 @@ protected Ruleset() /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; - + public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0fa714369354..ad1f9ec8979e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -115,7 +115,7 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb public GameplayState GameplayState { get; private set; } - protected Ruleset ruleset; + private Ruleset ruleset; public BreakOverlay BreakOverlay; diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 122ca291428d..e30d2b2d4285 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -7,12 +7,12 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class AnalysisSettings : PlayerSettingsGroup { - protected DrawableRuleset drawableRuleset; + protected DrawableRuleset DrawableRuleset; public AnalysisSettings(DrawableRuleset drawableRuleset) : base("Analysis Settings") { - this.drawableRuleset = drawableRuleset; + DrawableRuleset = drawableRuleset; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 0d877785e7fc..65e99731dc23 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,10 +72,9 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset); - if (analysisSettings != null) { + var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); - } } protected override void PrepareReplay() From 2a1fa8ccacd0ece5f6a098bd4951cefcd9a862c7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 22:34:38 -0500 Subject: [PATCH 08/43] fix OsuAnalysisSettings text not updating --- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 51fd835dc502..0411882d2aad 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; @@ -30,7 +31,8 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); From 4d669c56a2adb102031e1ea0c63538409ace3ba8 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:32:35 -0500 Subject: [PATCH 09/43] implement pooling Uses pooling for all analysis objects and creates the lifetime entries from replay data when the analysis container is constructed. --- .../UI/OsuAnalysisContainer.cs | 242 ++++++++++++++++++ .../UI/OsuAnalysisSettings.cs | 18 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +- osu.Game/Rulesets/UI/AnalysisContainer.cs | 18 ++ osu.Game/Rulesets/UI/Playfield.cs | 6 + .../Play/PlayerSettings/AnalysisSettings.cs | 5 +- osu.Game/Screens/Play/ReplayPlayer.cs | 3 + 7 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs create mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs new file mode 100644 index 000000000000..a637eddd05e5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -0,0 +1,242 @@ +// 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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; +using osu.Game.Replays; +using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class OsuAnalysisContainer : AnalysisContainer + { + public Bindable HitMarkerEnabled = new BindableBool(); + public Bindable AimMarkersEnabled = new BindableBool(); + public Bindable AimLinesEnabled = new BindableBool(); + + private HitMarkersContainer hitMarkersContainer; + private AimMarkersContainer aimMarkersContainer; + private AimLinesContainer aimLinesContainer; + + public OsuAnalysisContainer(Replay replay) + : base(replay) + { + InternalChildren = new Drawable[] + { + hitMarkersContainer = new HitMarkersContainer(), + aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue }, + aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue } + }; + + HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0); + AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0); + AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0); + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; + aimLineColor.A = 127; + + hitMarkersContainer.Hide(); + aimMarkersContainer.Hide(); + aimLinesContainer.Hide(); + + bool leftHeld = false; + bool rightHeld = false; + foreach (var frame in Replay.Frames) + { + var osuFrame = (OsuReplayFrame)frame; + + aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + + bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); + bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); + + if (leftHeld && !leftButton) + leftHeld = false; + else if (!leftHeld && leftButton) + { + hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + leftHeld = true; + } + + if (rightHeld && !rightButton) + rightHeld = false; + else if (!rightHeld && rightButton) + { + hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + rightHeld = true; + } + } + } + + private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly HitMarkerPool leftPool; + private readonly HitMarkerPool rightPool; + + public HitMarkersContainer() + { + AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15)); + AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15)); + } + + protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); + } + + private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly HitMarkerPool pool; + + public AimMarkersContainer() + { + AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80)); + } + + protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + } + + private partial class AimLinesContainer : Path + { + private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + + public AimLinesContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + + PathRadius = 1f; + Colour = new Color4(255, 255, 255, 127); + } + + protected override void Update() + { + base.Update(); + + lifetimeManager.Update(Time.Current); + } + + public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + + private void entryBecameAlive(LifetimeEntry entry) + { + aliveEntries.Add((AimPointEntry)entry); + updateVertices(); + } + + private void entryBecameDead(LifetimeEntry entry) + { + aliveEntries.Remove((AimPointEntry)entry); + updateVertices(); + } + + private void updateVertices() + { + ClearVertices(); + foreach (var entry in aliveEntries) + { + AddVertex(entry.Position); + } + } + + private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + { + + } + + private sealed class AimLinePointComparator : IComparer + { + public int Compare(AimPointEntry? x, AimPointEntry? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return x.LifetimeStart.CompareTo(y.LifetimeStart); + } + } + } + + private partial class HitMarkerDrawable : PoolableDrawableWithLifetime + { + /// + /// This constructor only exists to meet the new() type constraint of . + /// + public HitMarkerDrawable() + { + } + + public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action) + { + Origin = Anchor.Centre; + InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action)); + } + + protected override void OnApply(AimPointEntry entry) + { + Position = entry.Position; + + using (BeginAbsoluteSequence(LifetimeStart)) + Show(); + + using (BeginAbsoluteSequence(LifetimeEnd - 200)) + this.FadeOut(200); + } + } + + private partial class HitMarkerPool : DrawablePool + { + private readonly OsuSkinComponents component; + private readonly OsuAction? action; + + public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize) + : base(initialSize) + { + this.component = component; + this.action = action; + } + + protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action); + } + + private partial class AimPointEntry : LifetimeEntry + { + public Vector2 Position { get; } + + public AimPointEntry(double time, Vector2 position) + { + LifetimeStart = time; + LifetimeEnd = time + 1_000; + Position = position; + } + } + + private partial class HitMarkerEntry : AimPointEntry + { + public bool IsLeftMarker { get; } + + public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) + : base(lifetimeStart, position) + { + IsLeftMarker = isLeftMarker; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 0411882d2aad..fd9cb6799577 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -1,10 +1,10 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; +using osu.Game.Replays; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -29,14 +29,7 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } }; - } - [BackgroundDependencyLoader] - private void load() - { - DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); } @@ -52,5 +45,14 @@ private void onCursorToggle(ValueChangedEvent hide) DrawableRuleset.Playfield.Cursor.FadeIn(); } } + + public override AnalysisContainer CreateAnalysisContainer(Replay replay) + { + var analysisContainer = new OsuAnalysisContainer(replay); + analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); + return analysisContainer; + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 3cb3c50ef722..ea336e6067c0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -37,8 +37,6 @@ public partial class OsuPlayfield : Playfield public SmokeContainer Smoke { get; } - public HitMarkerContainer MarkersContainer { get; } - public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -61,8 +59,7 @@ public OsuPlayfield() judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, - approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both } + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs new file mode 100644 index 000000000000..62d54374e724 --- /dev/null +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -0,0 +1,18 @@ +// 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.Containers; +using osu.Game.Replays; + +namespace osu.Game.Rulesets.UI +{ + public partial class AnalysisContainer : Container + { + protected Replay Replay; + + public AnalysisContainer(Replay replay) + { + Replay = replay; + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 90a2f63faa8a..e116acdc1915 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -291,6 +291,12 @@ protected override void Update() /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + /// + /// Adds an analysis container to internal children for replays. + /// + /// + public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer); + #region Pooling support private readonly Dictionary pools = new Dictionary(); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index e30d2b2d4285..3752b2b900ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Replays; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.PlayerSettings { - public partial class AnalysisSettings : PlayerSettingsGroup + public abstract partial class AnalysisSettings : PlayerSettingsGroup { protected DrawableRuleset DrawableRuleset; @@ -14,5 +15,7 @@ public AnalysisSettings(DrawableRuleset drawableRuleset) { DrawableRuleset = drawableRuleset; } + + public abstract AnalysisContainer CreateAnalysisContainer(Replay replay); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 65e99731dc23..ce6cb5124a61 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -74,7 +74,10 @@ private void load(OsuConfigManager config) var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); if (analysisSettings != null) + { HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); + DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay)); + } } protected override void PrepareReplay() From c95e85368fab71e8c38e10f3dcf809f47f0986e3 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:45:58 -0500 Subject: [PATCH 10/43] remove old files --- .../TestSceneHitMarker.cs | 135 ------------ .../UI/HitMarkerContainer.cs | 195 ------------------ 2 files changed, 330 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs deleted file mode 100644 index 7ba681b50f1d..000000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Framework.Input.States; -using osu.Framework.Logging; -using osu.Framework.Testing.Input; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public partial class TestSceneHitMarker : OsuSkinnableTestScene - { - [Test] - public void TestHitMarkers() - { - var markerContainers = new List(); - - AddStep("Create hit markers", () => - { - markerContainers.Clear(); - SetContents(_ => - { - markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) - { - HitMarkerEnabled = { Value = true }, - AimMarkersEnabled = { Value = true }, - AimLinesEnabled = { Value = true }, - RelativeSizeAxes = Axes.Both - }); - - return new HitMarkerInputManager - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.95f), - Child = markerContainers[^1], - }; - }); - }); - - AddUntilStep("Until skinnable expires", () => - { - if (markerContainers.Count == 0) - return false; - - Logger.Log("How many: " + markerContainers.Count); - - foreach (var markerContainer in markerContainers) - { - if (markerContainer.Children.Count != 0) - return false; - } - - return true; - }); - } - - private partial class HitMarkerInputManager : ManualInputManager - { - private double? startTime; - - public HitMarkerInputManager() - { - UseParentInput = false; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - MoveMouseTo(ToScreenSpace(DrawSize / 2)); - } - - protected override void Update() - { - base.Update(); - - const float spin_angle = 4 * MathF.PI; - - startTime ??= Time.Current; - - float fraction = (float)((Time.Current - startTime) / 5_000); - - float angle = fraction * spin_angle; - float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; - - Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2; - MoveMouseTo(ToScreenSpace(pos)); - } - } - - private partial class TestHitMarkerContainer : HitMarkerContainer - { - private double? lastClick; - private double? startTime; - private bool finishedDrawing; - private bool leftOrRight; - - public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) - : base(hitObjectContainer) - { - } - - protected override void Update() - { - base.Update(); - - if (finishedDrawing) - return; - - startTime ??= lastClick ??= Time.Current; - - if (startTime + 5_000 <= Time.Current) - { - finishedDrawing = true; - HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false; - return; - } - - if (lastClick + 400 <= Time.Current) - { - OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton)); - leftOrRight = !leftOrRight; - lastClick = Time.Current; - } - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs deleted file mode 100644 index e8916ea5451a..000000000000 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ /dev/null @@ -1,195 +0,0 @@ -// 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.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Skinning; -using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Rulesets.UI; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI -{ - public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler - { - private Vector2 lastMousePosition; - private Vector2? lastlastMousePosition; - private double timePreempt; - - public Bindable HitMarkerEnabled = new BindableBool(); - public Bindable AimMarkersEnabled = new BindableBool(); - public Bindable AimLinesEnabled = new BindableBool(); - - private const double default_time_preempt = 1000; - - private readonly HitObjectContainer hitObjectContainer; - - public override bool ReceivePositionalInputAt(Vector2 _) => true; - - public HitMarkerContainer(HitObjectContainer hitObjectContainer) - { - this.hitObjectContainer = hitObjectContainer; - timePreempt = default_time_preempt; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) - { - updateTimePreempt(); - addMarker(e.Action); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - lastlastMousePosition = lastMousePosition; - lastMousePosition = e.MousePosition; - - if (AimMarkersEnabled.Value) - { - updateTimePreempt(); - addMarker(null); - } - - if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) - { - if (!AimMarkersEnabled.Value) - updateTimePreempt(); - Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt)); - } - - return base.OnMouseMove(e); - } - - private void addMarker(OsuAction? action) - { - var component = OsuSkinComponents.AimMarker; - - switch (action) - { - case OsuAction.LeftButton: - component = OsuSkinComponents.HitMarkerLeft; - break; - - case OsuAction.RightButton: - component = OsuSkinComponents.HitMarkerRight; - break; - } - - Add(new HitMarkerDrawable(action, component, timePreempt) - { - Position = lastMousePosition, - Origin = Anchor.Centre, - Depth = action == null ? float.MaxValue : float.MinValue - }); - } - - private void updateTimePreempt() - { - var hitObject = getHitObject(); - if (hitObject == null) - return; - - timePreempt = hitObject.TimePreempt; - } - - private OsuHitObject? getHitObject() - { - foreach (var dho in hitObjectContainer.Objects) - return (dho as DrawableOsuHitObject)?.HitObject; - - return null; - } - - private partial class HitMarkerDrawable : SkinnableDrawable - { - private readonly double lifetimeDuration; - private readonly double fadeOutTime; - - public override bool RemoveWhenNotAlive => true; - - public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt) - : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action)) - { - fadeOutTime = timePreempt / 2; - lifetimeDuration = timePreempt + fadeOutTime; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetimeDuration; - - Scheduler.AddDelayed(() => - { - this.FadeOut(fadeOutTime); - }, lifetimeDuration - fadeOutTime); - } - } - - private partial class AimLineDrawable : CompositeDrawable - { - private readonly double lifetimeDuration; - private readonly double fadeOutTime; - - public override bool RemoveWhenNotAlive => true; - - public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt) - { - fadeOutTime = timePreempt / 2; - lifetimeDuration = timePreempt + fadeOutTime; - - float distance = Vector2.Distance(fromP, toP); - Vector2 direction = (toP - fromP); - InternalChild = new Box - { - Position = fromP + (direction / 2), - Size = new Vector2(distance, 1), - Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)), - Origin = Anchor.Centre - }; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; - color.A = 127; - InternalChild.Colour = color; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetimeDuration; - - Scheduler.AddDelayed(() => - { - this.FadeOut(fadeOutTime); - }, lifetimeDuration - fadeOutTime); - } - } - } -} From 8cdd9c9ddcec526dcf4866f2c0b0ce1b98dbffd4 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:46:50 -0500 Subject: [PATCH 11/43] skinning changes improve default design --- .../Skinning/Default/DefaultHitMarker.cs | 8 +++----- osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 1 - osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 +----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs index 7dabb5182f70..dc890d4d637d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -23,7 +23,6 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(3, length), - Rotation = 45, Colour = Colour4.Black.Opacity(0.5F) }, new Box @@ -31,7 +30,7 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(3, length), - Rotation = 135, + Rotation = 90, Colour = Colour4.Black.Opacity(0.5F) } }; @@ -44,7 +43,6 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(1, length), - Rotation = 45, Colour = colour }, new Box @@ -52,7 +50,7 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(1, length), - Rotation = 135, + Rotation = 90, Colour = colour } }); @@ -69,7 +67,7 @@ public DefaultHitMarker(OsuAction? action) return (Colour4.LightGreen, 20, true); default: - return (Colour4.Gray.Opacity(0.3F), 8, false); + return (Colour4.Gray.Opacity(0.5F), 8, false); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 5c864fb6c21a..24f9217a5f45 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -10,6 +10,5 @@ public enum OsuSkinColour SliderBall, SpinnerBackground, StarBreakAdditive, - ReplayAimLine, } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index a637eddd05e5..68686f835d0d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -12,7 +12,6 @@ using osu.Game.Replays; using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -47,11 +46,8 @@ public OsuAnalysisContainer(Replay replay) } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load() { - var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; - aimLineColor.A = 127; - hitMarkersContainer.Hide(); aimMarkersContainer.Hide(); aimLinesContainer.Hide(); From 822ecb71068b77e071fb590983ff2834b0f9b2e3 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:52:17 -0500 Subject: [PATCH 12/43] remove unnecessary changes --- osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini | 5 +---- osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 ------ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini index 2952948f452a..9d16267d733b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini @@ -1,7 +1,4 @@ [General] Version: latest HitCircleOverlayAboveNumber: 0 -HitCirclePrefix: display - -[Colours] -ReplayAimLine: 0,0,255 \ No newline at end of file +HitCirclePrefix: display \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 68686f835d0d..c4b5135ca2e4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -117,7 +117,6 @@ public AimLinesContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; - lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; PathRadius = 1f; Colour = new Color4(255, 255, 255, 127); @@ -153,11 +152,6 @@ private void updateVertices() } } - private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) - { - - } - private sealed class AimLinePointComparator : IComparer { public int Compare(AimPointEntry? x, AimPointEntry? y) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ea336e6067c0..a801e3d2f7aa 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -59,7 +59,7 @@ public OsuPlayfield() judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, - approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both } + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; HitPolicy = new StartTimeOrderedHitPolicy(); From 29e5f409bac928bc49e1940a2e44b8ee4b8f2a70 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 21:51:54 -0500 Subject: [PATCH 13/43] remove skinnable better to add skinnable elements later --- .../Resources/special-skin/aimmarker@2x.png | Bin 648 -> 0 bytes .../special-skin/hitmarker-left@2x.png | Bin 2127 -> 0 bytes .../special-skin/hitmarker-right@2x.png | Bin 1742 -> 0 bytes osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 - .../Skinning/Default/DefaultHitMarker.cs | 74 ------------------ .../Skinning/Default/HitMarker.cs | 68 ++++++++++++---- .../Legacy/OsuLegacySkinTransformer.cs | 20 +---- .../UI/OsuAnalysisContainer.cs | 69 ++++++++-------- 8 files changed, 88 insertions(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png deleted file mode 100644 index 0b2a554193f08f7984568980956a3db8a6b39800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@ z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*= zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC> zq=ruJv2<)!2?D2qeSqx&#}JNi2t zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_ z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq? zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|# zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_ z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE% zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1? zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O! zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6 zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8* zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m&#*Br_F z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y- z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk FV1jr`?veli diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png deleted file mode 100644 index 66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1742 zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw? zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub! zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 75db18d345e9..52fdfea95f96 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -22,8 +22,5 @@ public enum OsuSkinComponents SpinnerBody, CursorSmoke, ApproachCircle, - HitMarkerLeft, - HitMarkerRight, - AimMarker } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs deleted file mode 100644 index dc890d4d637d..000000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public partial class DefaultHitMarker : CompositeDrawable - { - public DefaultHitMarker(OsuAction? action) - { - var (colour, length, hasBorder) = getConfig(action); - - if (hasBorder) - { - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - } - }; - } - - AddRangeInternal(new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - Colour = colour - } - }); - } - - private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) - { - switch (action) - { - case OsuAction.LeftButton: - return (Colour4.Orange, 20, true); - - case OsuAction.RightButton: - return (Colour4.LightGreen, 20, true); - - default: - return (Colour4.Gray.Opacity(0.5F), 8, false); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs index 28877345d0b0..fe662470bcd0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -1,37 +1,73 @@ // 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.Allocation; -using osu.Framework.Graphics.Sprites; -using osu.Game.Skinning; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public partial class HitMarker : Sprite + public partial class HitMarker : CompositeDrawable { - private readonly OsuAction? action; - - public HitMarker(OsuAction? action = null) + public HitMarker(OsuAction? action) { - this.action = action; + var (colour, length, hasBorder) = getConfig(action); + + if (hasBorder) + { + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + } + }; + } + + AddRangeInternal(new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + Colour = colour + } + }); } - [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) { switch (action) { case OsuAction.LeftButton: - Texture = skin.GetTexture(@"hitmarker-left"); - break; + return (Colour4.Orange, 20, true); case OsuAction.RightButton: - Texture = skin.GetTexture(@"hitmarker-right"); - break; + return (Colour4.LightGreen, 20, true); default: - Texture = skin.GetTexture(@"aimmarker"); - break; + return (Colour4.Gray.Opacity(0.5F), 8, false); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 61f9eebd862f..d2ebc68c5225 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; -using osu.Game.Rulesets.Osu.Skinning.Default; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -169,23 +168,8 @@ public OsuLegacySkinTransformer(ISkin skin) return null; - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; + default: + throw new UnsupportedSkinComponentException(lookup); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index c4b5135ca2e4..7d8ae6980cda 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -26,40 +25,41 @@ public partial class OsuAnalysisContainer : AnalysisContainer public Bindable AimMarkersEnabled = new BindableBool(); public Bindable AimLinesEnabled = new BindableBool(); - private HitMarkersContainer hitMarkersContainer; - private AimMarkersContainer aimMarkersContainer; - private AimLinesContainer aimLinesContainer; + protected HitMarkersContainer HitMarkers; + protected AimMarkersContainer AimMarkers; + protected AimLinesContainer AimLines; public OsuAnalysisContainer(Replay replay) : base(replay) { InternalChildren = new Drawable[] { - hitMarkersContainer = new HitMarkersContainer(), - aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue }, - aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue } + HitMarkers = new HitMarkersContainer(), + AimMarkers = new AimMarkersContainer { Depth = float.MinValue }, + AimLines = new AimLinesContainer { Depth = float.MaxValue } }; - HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0); - AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0); - AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0); + HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); + AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); + AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); } [BackgroundDependencyLoader] private void load() { - hitMarkersContainer.Hide(); - aimMarkersContainer.Hide(); - aimLinesContainer.Hide(); + HitMarkers.Hide(); + AimMarkers.Hide(); + AimLines.Hide(); bool leftHeld = false; bool rightHeld = false; + foreach (var frame in Replay.Frames) { var osuFrame = (OsuReplayFrame)frame; - aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -68,7 +68,7 @@ private void load() leftHeld = false; else if (!leftHeld && leftButton) { - hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); leftHeld = true; } @@ -76,42 +76,42 @@ private void load() rightHeld = false; else if (!rightHeld && rightButton) { - hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); rightHeld = true; } } } - private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer { private readonly HitMarkerPool leftPool; private readonly HitMarkerPool rightPool; public HitMarkersContainer() { - AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15)); - AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15)); + AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15)); + AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15)); } protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); } - private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer { private readonly HitMarkerPool pool; public AimMarkersContainer() { - AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80)); + AddInternal(pool = new HitMarkerPool(null, 80)); } protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); } - private partial class AimLinesContainer : Path + protected partial class AimLinesContainer : Path { - private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); public AimLinesContainer() { @@ -146,6 +146,7 @@ private void entryBecameDead(LifetimeEntry entry) private void updateVertices() { ClearVertices(); + foreach (var entry in aliveEntries) { AddVertex(entry.Position); @@ -164,7 +165,7 @@ public int Compare(AimPointEntry? x, AimPointEntry? y) } } - private partial class HitMarkerDrawable : PoolableDrawableWithLifetime + protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime { /// /// This constructor only exists to meet the new() type constraint of . @@ -173,10 +174,10 @@ public HitMarkerDrawable() { } - public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action) + public HitMarkerDrawable(OsuAction? action) { Origin = Anchor.Centre; - InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action)); + InternalChild = new HitMarker(action); } protected override void OnApply(AimPointEntry entry) @@ -191,22 +192,20 @@ protected override void OnApply(AimPointEntry entry) } } - private partial class HitMarkerPool : DrawablePool + protected partial class HitMarkerPool : DrawablePool { - private readonly OsuSkinComponents component; private readonly OsuAction? action; - public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize) + public HitMarkerPool(OsuAction? action, int initialSize) : base(initialSize) { - this.component = component; this.action = action; } - protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action); + protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action); } - private partial class AimPointEntry : LifetimeEntry + protected partial class AimPointEntry : LifetimeEntry { public Vector2 Position { get; } @@ -218,7 +217,7 @@ public AimPointEntry(double time, Vector2 position) } } - private partial class HitMarkerEntry : AimPointEntry + protected partial class HitMarkerEntry : AimPointEntry { public bool IsLeftMarker { get; } From cefc8357bbdb732f805e848b065afcf40619b9c0 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 21:52:10 -0500 Subject: [PATCH 14/43] test scene for OsuAnalysisContainer --- .../TestSceneHitMarker.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs new file mode 100644 index 000000000000..e4c48f96b89d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneHitMarker : OsuTestScene + { + private TestOsuAnalysisContainer analysisContainer; + + [Test] + public void TestHitMarkers() + { + createAnalysisContainer(); + AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true); + AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false); + AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + } + + [Test] + public void TestAimMarker() + { + createAnalysisContainer(); + AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true); + AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false); + AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + } + + [Test] + public void TestAimLines() + { + createAnalysisContainer(); + AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true); + AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false); + AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + } + + private void createAnalysisContainer() + { + AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay())); + } + + private Replay fabricateReplay() + { + var frames = new List(); + + for (int i = 0; i < 50; i++) + { + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(20 + i * 10, 20), + Actions = + { + i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton + } + }); + } + + return new Replay { Frames = frames }; + } + + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer(Replay replay) + : base(replay) + { + } + + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); + + public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); + + public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + } + } +} From 7687ab63edd2b49b4e1a5a2c30be2b9e2b8ca328 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 22:03:45 -0500 Subject: [PATCH 15/43] fix code formatting --- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 3 ++- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index fd9cb6799577..b3d5231adec4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -16,12 +16,13 @@ public partial class OsuAnalysisSettings : AnalysisSettings private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; - private readonly PlayerCheckbox hideCursorToggle; private readonly PlayerCheckbox aimLinesToggle; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) { + PlayerCheckbox hideCursorToggle; + Children = new Drawable[] { hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a801e3d2f7aa..411a02c5aff3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -36,7 +36,6 @@ public partial class OsuPlayfield : Playfield private readonly JudgementPooler judgementPooler; public SmokeContainer Smoke { get; } - public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 3752b2b900ac..91531b28b407 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -10,7 +10,7 @@ public abstract partial class AnalysisSettings : PlayerSettingsGroup { protected DrawableRuleset DrawableRuleset; - public AnalysisSettings(DrawableRuleset drawableRuleset) + protected AnalysisSettings(DrawableRuleset drawableRuleset) : base("Analysis Settings") { DrawableRuleset = drawableRuleset; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index ce6cb5124a61..81cc44d889ba 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -73,6 +73,7 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) { HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); From a2b15fcdee7feccabe605e06984c0b851843de31 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 00:59:42 -0400 Subject: [PATCH 16/43] rework code logic to make more sense analysis container creates settings and the settings items are created more nicely also removed use of localized strings because that can be done separately --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +- .../UI/OsuAnalysisContainer.cs | 35 +++++++----- .../UI/OsuAnalysisSettings.cs | 53 ++++--------------- .../PlayerSettingsOverlayStrings.cs | 20 ------- osu.Game/Rulesets/Ruleset.cs | 4 +- osu.Game/Rulesets/UI/AnalysisContainer.cs | 13 ++++- .../Play/PlayerSettings/AnalysisSettings.cs | 13 ++--- osu.Game/Screens/Play/ReplayPlayer.cs | 8 +-- 8 files changed, 55 insertions(+), 95 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8beeeac34e4c..a8a1d98bf3ad 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -37,7 +38,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -361,7 +361,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); + public override OsuAnalysisContainer CreateAnalysisContainer(Replay replay, Playfield playfield) => new OsuAnalysisContainer(replay, playfield); public override bool EditorShowScrollSpeed => false; } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 7d8ae6980cda..3bddc479ef93 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -1,10 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. + +// 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.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; @@ -21,27 +21,33 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisContainer : AnalysisContainer { - public Bindable HitMarkerEnabled = new BindableBool(); - public Bindable AimMarkersEnabled = new BindableBool(); - public Bindable AimLinesEnabled = new BindableBool(); + public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; + + protected new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; protected HitMarkersContainer HitMarkers; protected AimMarkersContainer AimMarkers; protected AimLinesContainer AimLines; - public OsuAnalysisContainer(Replay replay) - : base(replay) + public OsuAnalysisContainer(Replay replay, Playfield playfield) + : base(replay, playfield) { InternalChildren = new Drawable[] { + AimLines = new AimLinesContainer { Depth = float.MaxValue }, HitMarkers = new HitMarkersContainer(), - AimMarkers = new AimMarkersContainer { Depth = float.MinValue }, - AimLines = new AimLinesContainer { Depth = float.MaxValue } + AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; + } - HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); - AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); - AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); + protected override OsuAnalysisSettings CreateAnalysisSettings() + { + var settings = new OsuAnalysisSettings(); + settings.HitMarkersEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); + settings.AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); + settings.AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); + settings.CursorHideEnabled.ValueChanged += e => Playfield.Cursor.FadeTo(e.NewValue ? 0 : 1); + return settings; } [BackgroundDependencyLoader] @@ -51,6 +57,11 @@ private void load() AimMarkers.Hide(); AimLines.Hide(); + LoadReplay(); + } + + protected void LoadReplay() + { bool leftHeld = false; bool rightHeld = false; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index b3d5231adec4..c45b893a1c6c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -2,58 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Localisation; -using osu.Game.Replays; -using osu.Game.Rulesets.UI; +using osu.Game.Configuration; using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool HitMarkersEnabled { get; } = new BindableBool(); - private readonly PlayerCheckbox hitMarkerToggle; - private readonly PlayerCheckbox aimMarkerToggle; - private readonly PlayerCheckbox aimLinesToggle; + [SettingSource("Aim markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool AimMarkersEnabled { get; } = new BindableBool(); - public OsuAnalysisSettings(DrawableRuleset drawableRuleset) - : base(drawableRuleset) - { - PlayerCheckbox hideCursorToggle; + [SettingSource("Aim lines", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool AimLinesEnabled { get; } = new BindableBool(); - Children = new Drawable[] - { - hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, - aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, - aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } - }; - - hideCursorToggle.Current.BindValueChanged(onCursorToggle); - } - - private void onCursorToggle(ValueChangedEvent hide) - { - // this only hides half the cursor - if (hide.NewValue) - { - DrawableRuleset.Playfield.Cursor.FadeOut(); - } - else - { - DrawableRuleset.Playfield.Cursor.FadeIn(); - } - } - - public override AnalysisContainer CreateAnalysisContainer(Replay replay) - { - var analysisContainer = new OsuAnalysisContainer(replay); - analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); - return analysisContainer; - } + [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool CursorHideEnabled { get; } = new BindableBool(); } } diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index 017cc9bf82c4..60874da561ff 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -19,26 +19,6 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame"); - /// - /// "Hit markers" - /// - public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers"); - - /// - /// "Aim markers" - /// - public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers"); - - /// - /// "Hide cursor" - /// - public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); - - /// - /// "Aim lines" - /// - public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines"); - /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 4626698190df..fdf43c2f0961 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -17,6 +17,7 @@ using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Overlays.Settings; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -27,7 +28,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; @@ -410,6 +410,6 @@ public virtual IEnumerable CreateEditorSetupSections() => public virtual DifficultySection? CreateEditorDifficultySection() => null; - public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; + public virtual AnalysisContainer? CreateAnalysisContainer(Replay replay, Playfield playfield) => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs index 62d54374e724..69a71cf06ef5 100644 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -3,16 +3,25 @@ using osu.Framework.Graphics.Containers; using osu.Game.Replays; +using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.UI { - public partial class AnalysisContainer : Container + public abstract partial class AnalysisContainer : Container { protected Replay Replay; + protected Playfield Playfield; - public AnalysisContainer(Replay replay) + public AnalysisSettings AnalysisSettings; + + public AnalysisContainer(Replay replay, Playfield playfield) { Replay = replay; + Playfield = playfield; + + AnalysisSettings = CreateAnalysisSettings(); } + + protected abstract AnalysisSettings CreateAnalysisSettings(); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 91531b28b407..e1f77cef1274 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -1,21 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Replays; -using osu.Game.Rulesets.UI; +using osu.Game.Configuration; namespace osu.Game.Screens.Play.PlayerSettings { - public abstract partial class AnalysisSettings : PlayerSettingsGroup + public partial class AnalysisSettings : PlayerSettingsGroup { - protected DrawableRuleset DrawableRuleset; - - protected AnalysisSettings(DrawableRuleset drawableRuleset) + public AnalysisSettings() : base("Analysis Settings") { - DrawableRuleset = drawableRuleset; + AddRange(this.CreateSettingsControls()); } - - public abstract AnalysisContainer CreateAnalysisContainer(Replay replay); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 5d604003c8cb..af9568c08c89 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,12 +72,12 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + var analysisContainer = DrawableRuleset.Ruleset.CreateAnalysisContainer(GameplayState.Score.Replay, DrawableRuleset.Playfield); - if (analysisSettings != null) + if (analysisContainer != null) { - HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); - DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay)); + HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisContainer.AnalysisSettings); + DrawableRuleset.Playfield.AddAnalysisContainer(analysisContainer); } } From 56db29d0f5c7d798144660186ededd38a8ff03ae Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 01:38:54 -0400 Subject: [PATCH 17/43] make test go indefinitely --- .../TestSceneHitMarker.cs | 91 ------------ .../TestSceneOsuAnalysisContainer.cs | 134 ++++++++++++++++++ .../UI/OsuAnalysisContainer.cs | 2 + 3 files changed, 136 insertions(+), 91 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs deleted file mode 100644 index e4c48f96b89d..000000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Replays; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Replays; -using osu.Game.Tests.Visual; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public partial class TestSceneHitMarker : OsuTestScene - { - private TestOsuAnalysisContainer analysisContainer; - - [Test] - public void TestHitMarkers() - { - createAnalysisContainer(); - AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true); - AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false); - AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); - } - - [Test] - public void TestAimMarker() - { - createAnalysisContainer(); - AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true); - AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false); - AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); - } - - [Test] - public void TestAimLines() - { - createAnalysisContainer(); - AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true); - AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false); - AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); - } - - private void createAnalysisContainer() - { - AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay())); - } - - private Replay fabricateReplay() - { - var frames = new List(); - - for (int i = 0; i < 50; i++) - { - frames.Add(new OsuReplayFrame - { - Time = Time.Current + i * 15, - Position = new Vector2(20 + i * 10, 20), - Actions = - { - i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton - } - }); - } - - return new Replay { Frames = frames }; - } - - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer - { - public TestOsuAnalysisContainer(Replay replay) - : base(replay) - { - } - - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs new file mode 100644 index 000000000000..a17325655725 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Threading; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneOsuAnalysisContainer : OsuTestScene + { + private TestOsuAnalysisContainer analysisContainer; + + [BackgroundDependencyLoader] + private void load() + { + Child = analysisContainer = createAnalysisContainer(); + } + + [Test] + public void TestHitMarkers() + { + var loop = createAnalysisContainer(); + AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); + AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); + AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + } + + [Test] + public void TestAimMarker() + { + var loop = createAnalysisContainer(); + AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); + AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); + AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + } + + [Test] + public void TestAimLines() + { + var loop = createAnalysisContainer(); + AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); + AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); + AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + } + + private TestOsuAnalysisContainer createAnalysisContainer() => new TestOsuAnalysisContainer(); + + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer() + : base(new Replay(), new OsuPlayfield()) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Replay = fabricateReplay(); + LoadReplay(); + + makeReplayLoop(); + } + + private void makeReplayLoop() + { + Scheduler.AddDelayed(() => + { + Replay = fabricateReplay(); + + HitMarkers.Clear(); + AimMarkers.Clear(); + AimLines.Clear(); + + LoadReplay(); + + makeReplayLoop(); + }, 15000); + } + + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); + + public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); + + public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + + private Replay fabricateReplay() + { + var frames = new List(); + var random = new Random(); + int posX = 250; + int posY = 250; + bool leftOrRight = false; + + for (int i = 0; i < 1000; i++) + { + posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); + posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); + + var actions = new List(); + + if (i % 20 == 0) + { + actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); + leftOrRight = !leftOrRight; + } + + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(posX, posY), + Actions = actions + }); + } + + return new Replay { Frames = frames }; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 3bddc479ef93..2f341a35bb0a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -142,6 +142,8 @@ protected override void Update() public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + public void Clear() => lifetimeManager.ClearEntries(); + private void entryBecameAlive(LifetimeEntry entry) { aliveEntries.Add((AimPointEntry)entry); From a549cdd5b9f1b8a968e9a3e5d57c926db22b8675 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 04:49:50 -0400 Subject: [PATCH 18/43] persist analysis settings --- .../UI/OsuAnalysisContainer.cs | 27 ++++++++++++------- .../UI/OsuAnalysisSettings.cs | 10 +++++++ osu.Game/Configuration/OsuConfigManager.cs | 10 +++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 2f341a35bb0a..4eff1477727f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -1,5 +1,4 @@ - -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -38,28 +37,38 @@ public OsuAnalysisContainer(Replay replay, Playfield playfield) HitMarkers = new HitMarkersContainer(), AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; + } protected override OsuAnalysisSettings CreateAnalysisSettings() { var settings = new OsuAnalysisSettings(); - settings.HitMarkersEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); - settings.AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); - settings.AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); - settings.CursorHideEnabled.ValueChanged += e => Playfield.Cursor.FadeTo(e.NewValue ? 0 : 1); + settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); + settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); + settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); + settings.CursorHideEnabled.ValueChanged += e => toggleCursorHidden(e.NewValue); return settings; } [BackgroundDependencyLoader] private void load() { - HitMarkers.Hide(); - AimMarkers.Hide(); - AimLines.Hide(); + toggleHitMarkers(AnalysisSettings.HitMarkersEnabled.Value); + toggleAimMarkers(AnalysisSettings.AimMarkersEnabled.Value); + toggleAimLines(AnalysisSettings.AimLinesEnabled.Value); + toggleCursorHidden(AnalysisSettings.CursorHideEnabled.Value); LoadReplay(); } + private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); + + private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); + + private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); + + private void toggleCursorHidden(bool value) => Playfield.Cursor.FadeTo(value ? 0 : 1); + protected void LoadReplay() { bool leftHeld = false; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index c45b893a1c6c..ae81b2c0b8f4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Screens.Play.PlayerSettings; @@ -20,5 +21,14 @@ public partial class OsuAnalysisSettings : AnalysisSettings [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); + config.BindWith(OsuSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); + config.BindWith(OsuSetting.ReplayAimLinesEnabled, AimLinesEnabled); + config.BindWith(OsuSetting.ReplayCursorHideEnabled, CursorHideEnabled); + } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8d6c244b350c..8b75c9c934cd 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -154,6 +154,12 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); SetDefault(OsuSetting.GameplayDisableWinKey, true); + // Replay + SetDefault(OsuSetting.ReplayHitMarkersEnabled, false); + SetDefault(OsuSetting.ReplayAimMarkersEnabled, false); + SetDefault(OsuSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuSetting.ReplayCursorHideEnabled, false); + // Update SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -413,6 +419,10 @@ public enum OsuSetting EditorShowHitMarkers, EditorAutoSeekOnPlacement, DiscordRichPresence, + ReplayHitMarkersEnabled, + ReplayAimMarkersEnabled, + ReplayAimLinesEnabled, + ReplayCursorHideEnabled, ShowOnlineExplicitContent, LastProcessedMetadataId, From c89597b060cae084899db82c3b6c5040a17f794a Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Wed, 4 Sep 2024 03:37:52 -0400 Subject: [PATCH 19/43] fix config mistake --- .../Configuration/OsuRulesetConfigManager.cs | 11 +++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ .../UI/OsuAnalysisContainer.cs | 13 ++++++------- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 17 ++++++++++++----- osu.Game/Configuration/OsuConfigManager.cs | 10 ---------- osu.Game/Rulesets/Ruleset.cs | 3 --- osu.Game/Rulesets/UI/AnalysisContainer.cs | 10 +++++----- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 ++ .../Play/PlayerSettings/AnalysisSettings.cs | 7 ++++++- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- 11 files changed, 45 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 2056a50edab2..23b7b9c1faa2 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -24,6 +24,11 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ShowCursorTrail, true); SetDefault(OsuRulesetSetting.ShowCursorRipples, false); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); + + SetDefault(OsuRulesetSetting.ReplayHitMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAimMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); } } @@ -34,5 +39,11 @@ public enum OsuRulesetSetting ShowCursorTrail, ShowCursorRipples, PlayfieldBorderStyle, + + // Replay + ReplayHitMarkersEnabled, + ReplayAimMarkersEnabled, + ReplayAimLinesEnabled, + ReplayCursorHideEnabled, } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a8a1d98bf3ad..be48ef9acc47 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -13,7 +13,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays.Settings; -using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -361,8 +360,6 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override OsuAnalysisContainer CreateAnalysisContainer(Replay replay, Playfield playfield) => new OsuAnalysisContainer(replay, playfield); - public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index f0390ad716d5..3c6456957b92 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -68,5 +68,7 @@ public override double GameplayStartTime return 0; } } + + public override AnalysisContainer CreateAnalysisContainer(Replay replay) => new OsuAnalysisContainer(replay, this); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 4eff1477727f..57401edece29 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -22,14 +22,14 @@ public partial class OsuAnalysisContainer : AnalysisContainer { public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; - protected new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; + protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; protected HitMarkersContainer HitMarkers; protected AimMarkersContainer AimMarkers; protected AimLinesContainer AimLines; - public OsuAnalysisContainer(Replay replay, Playfield playfield) - : base(replay, playfield) + public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) { InternalChildren = new Drawable[] { @@ -37,12 +37,11 @@ public OsuAnalysisContainer(Replay replay, Playfield playfield) HitMarkers = new HitMarkersContainer(), AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; - } - protected override OsuAnalysisSettings CreateAnalysisSettings() + protected override OsuAnalysisSettings CreateAnalysisSettings(Ruleset ruleset) { - var settings = new OsuAnalysisSettings(); + var settings = new OsuAnalysisSettings((OsuRuleset)ruleset); settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); @@ -67,7 +66,7 @@ private void load() private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - private void toggleCursorHidden(bool value) => Playfield.Cursor.FadeTo(value ? 0 : 1); + private void toggleCursorHidden(bool value) => DrawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); protected void LoadReplay() { diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index ae81b2c0b8f4..6e11c87c3a8f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -4,12 +4,18 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { + public OsuAnalysisSettings(Ruleset ruleset) + : base(ruleset) + { + } + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,12 +29,13 @@ public partial class OsuAnalysisSettings : AnalysisSettings public BindableBool CursorHideEnabled { get; } = new BindableBool(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(IRulesetConfigCache cache) { - config.BindWith(OsuSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); - config.BindWith(OsuSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); - config.BindWith(OsuSetting.ReplayAimLinesEnabled, AimLinesEnabled); - config.BindWith(OsuSetting.ReplayCursorHideEnabled, CursorHideEnabled); + var config = (OsuRulesetConfigManager)cache.GetConfigFor(Ruleset)!; + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); + config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, CursorHideEnabled); } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8b75c9c934cd..8d6c244b350c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -154,12 +154,6 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); SetDefault(OsuSetting.GameplayDisableWinKey, true); - // Replay - SetDefault(OsuSetting.ReplayHitMarkersEnabled, false); - SetDefault(OsuSetting.ReplayAimMarkersEnabled, false); - SetDefault(OsuSetting.ReplayAimLinesEnabled, false); - SetDefault(OsuSetting.ReplayCursorHideEnabled, false); - // Update SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -419,10 +413,6 @@ public enum OsuSetting EditorShowHitMarkers, EditorAutoSeekOnPlacement, DiscordRichPresence, - ReplayHitMarkersEnabled, - ReplayAimMarkersEnabled, - ReplayAimLinesEnabled, - ReplayCursorHideEnabled, ShowOnlineExplicitContent, LastProcessedMetadataId, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index fdf43c2f0961..2e48b8e16fcc 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Overlays.Settings; -using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -409,7 +408,5 @@ public virtual IEnumerable CreateEditorSetupSections() => public virtual bool EditorShowScrollSpeed => true; public virtual DifficultySection? CreateEditorDifficultySection() => null; - - public virtual AnalysisContainer? CreateAnalysisContainer(Replay replay, Playfield playfield) => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs index 69a71cf06ef5..b6c2a8c1c805 100644 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -10,18 +10,18 @@ namespace osu.Game.Rulesets.UI public abstract partial class AnalysisContainer : Container { protected Replay Replay; - protected Playfield Playfield; + protected DrawableRuleset DrawableRuleset; public AnalysisSettings AnalysisSettings; - public AnalysisContainer(Replay replay, Playfield playfield) + protected AnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) { Replay = replay; - Playfield = playfield; + DrawableRuleset = drawableRuleset; - AnalysisSettings = CreateAnalysisSettings(); + AnalysisSettings = CreateAnalysisSettings(drawableRuleset.Ruleset); } - protected abstract AnalysisSettings CreateAnalysisSettings(); + protected abstract AnalysisSettings CreateAnalysisSettings(Ruleset ruleset); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a28b2716cb7b..5b1f59d549ad 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -596,6 +596,8 @@ public HitWindows FirstAvailableHitWindows /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); + + public virtual AnalysisContainer CreateAnalysisContainer(Replay replay) => null; } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index e1f77cef1274..4c64eef92f9d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -2,14 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Configuration; +using osu.Game.Rulesets; namespace osu.Game.Screens.Play.PlayerSettings { public partial class AnalysisSettings : PlayerSettingsGroup { - public AnalysisSettings() + protected Ruleset Ruleset; + + public AnalysisSettings(Ruleset ruleset) : base("Analysis Settings") { + Ruleset = ruleset; + AddRange(this.CreateSettingsControls()); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index af9568c08c89..82a2f0925040 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,7 +72,7 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisContainer = DrawableRuleset.Ruleset.CreateAnalysisContainer(GameplayState.Score.Replay, DrawableRuleset.Playfield); + var analysisContainer = DrawableRuleset.CreateAnalysisContainer(GameplayState.Score.Replay); if (analysisContainer != null) { From 59ff8c498417f97760b83ccf55fdfb65fd454428 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Wed, 4 Sep 2024 03:38:13 -0400 Subject: [PATCH 20/43] fix analysis container creation --- .../TestSceneOsuAnalysisContainer.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index a17325655725..0fc91513e62b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -8,11 +8,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Threading; using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; using osuTK; @@ -31,7 +32,6 @@ private void load() [Test] public void TestHitMarkers() { - var loop = createAnalysisContainer(); AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); @@ -41,7 +41,6 @@ public void TestHitMarkers() [Test] public void TestAimMarker() { - var loop = createAnalysisContainer(); AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); @@ -51,19 +50,28 @@ public void TestAimMarker() [Test] public void TestAimLines() { - var loop = createAnalysisContainer(); AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } - private TestOsuAnalysisContainer createAnalysisContainer() => new TestOsuAnalysisContainer(); + private TestOsuAnalysisContainer createAnalysisContainer() + { + var replay = new Replay(); + var ruleset = new OsuRuleset(); + var beatmap = new OsuBeatmap(); + var drawableRuleset = new DrawableOsuRuleset(ruleset, beatmap); + // Load playfield cursor to avoid errors + Add(drawableRuleset); + + return new TestOsuAnalysisContainer(replay, drawableRuleset); + } private partial class TestOsuAnalysisContainer : OsuAnalysisContainer { - public TestOsuAnalysisContainer() - : base(new Replay(), new OsuPlayfield()) + public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) { } From a417fec2347a01bb4e2ab1be308c474f4a240ab3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:35:27 +0900 Subject: [PATCH 21/43] Move analysis container implementation completely local to osu! ruleset --- .../TestSceneOsuAnalysisContainer.cs | 129 +++++++----------- .../UI/DrawableOsuRuleset.cs | 10 +- .../UI/OsuAnalysisContainer.cs | 51 +++---- .../UI/OsuAnalysisSettings.cs | 17 ++- osu.Game/Rulesets/Ruleset.cs | 2 - osu.Game/Rulesets/UI/AnalysisContainer.cs | 27 ---- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 - osu.Game/Rulesets/UI/Playfield.cs | 6 - .../Play/PlayerSettings/AnalysisSettings.cs | 21 --- osu.Game/Screens/Play/ReplayPlayer.cs | 8 -- 10 files changed, 93 insertions(+), 180 deletions(-) delete mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs delete mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 0fc91513e62b..536de8b41ac0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Replays; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Replays; @@ -21,122 +20,92 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneOsuAnalysisContainer : OsuTestScene { - private TestOsuAnalysisContainer analysisContainer; + private TestOsuAnalysisContainer analysisContainer = null!; - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = analysisContainer = createAnalysisContainer(); + AddStep("create analysis container", () => + { + DrawableOsuRuleset drawableRuleset = new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()); + + Children = new Drawable[] + { + drawableRuleset, + analysisContainer = new TestOsuAnalysisContainer(fabricateReplay(), drawableRuleset), + }; + }); } [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } - private TestOsuAnalysisContainer createAnalysisContainer() - { - var replay = new Replay(); - var ruleset = new OsuRuleset(); - var beatmap = new OsuBeatmap(); - var drawableRuleset = new DrawableOsuRuleset(ruleset, beatmap); - // Load playfield cursor to avoid errors - Add(drawableRuleset); - - return new TestOsuAnalysisContainer(replay, drawableRuleset); - } - - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + private Replay fabricateReplay() { - public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) - { - } + var frames = new List(); + var random = new Random(); + int posX = 250; + int posY = 250; + bool leftOrRight = false; - [BackgroundDependencyLoader] - private void load() + for (int i = 0; i < 1000; i++) { - Replay = fabricateReplay(); - LoadReplay(); + posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); + posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); - makeReplayLoop(); - } + var actions = new List(); - private void makeReplayLoop() - { - Scheduler.AddDelayed(() => + if (i % 20 == 0) { - Replay = fabricateReplay(); + actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); + leftOrRight = !leftOrRight; + } - HitMarkers.Clear(); - AimMarkers.Clear(); - AimLines.Clear(); + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(posX, posY), + Actions = actions + }); + } - LoadReplay(); + return new Replay { Frames = frames }; + } - makeReplayLoop(); - }, 15000); + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) + { } public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; - - private Replay fabricateReplay() - { - var frames = new List(); - var random = new Random(); - int posX = 250; - int posY = 250; - bool leftOrRight = false; - - for (int i = 0; i < 1000; i++) - { - posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); - posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); - - var actions = new List(); - - if (i % 20 == 0) - { - actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); - leftOrRight = !leftOrRight; - } - - frames.Add(new OsuReplayFrame - { - Time = Time.Current + i * 15, - Position = new Vector2(posX, posY), - Actions = actions - }); - } - - return new Replay { Frames = frames }; - } } } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 3c6456957b92..ba0768db5dea 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -36,6 +36,14 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList { } + protected override void LoadComplete() + { + if (HasReplayLoaded.Value) + LoadComponentAsync(new OsuAnalysisContainer(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); + + base.LoadComplete(); + } + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor @@ -68,7 +76,5 @@ public override double GameplayStartTime return 0; } } - - public override AnalysisContainer CreateAnalysisContainer(Replay replay) => new OsuAnalysisContainer(replay, this); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 57401edece29..19e53944c964 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -18,62 +19,62 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisContainer : AnalysisContainer + public partial class OsuAnalysisContainer : CompositeDrawable { - public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; + protected readonly HitMarkersContainer HitMarkers; + protected readonly AimMarkersContainer AimMarkers; + protected readonly AimLinesContainer AimLines; - protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; + public OsuAnalysisSettings Settings = null!; - protected HitMarkersContainer HitMarkers; - protected AimMarkersContainer AimMarkers; - protected AimLinesContainer AimLines; + private readonly Replay replay; + private readonly DrawableRuleset drawableRuleset; public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) { + this.replay = replay; + this.drawableRuleset = drawableRuleset; + InternalChildren = new Drawable[] { - AimLines = new AimLinesContainer { Depth = float.MaxValue }, HitMarkers = new HitMarkersContainer(), - AimMarkers = new AimMarkersContainer { Depth = float.MinValue } + AimLines = new AimLinesContainer(), + AimMarkers = new AimMarkersContainer(), }; } - protected override OsuAnalysisSettings CreateAnalysisSettings(Ruleset ruleset) - { - var settings = new OsuAnalysisSettings((OsuRuleset)ruleset); - settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); - settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); - settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); - settings.CursorHideEnabled.ValueChanged += e => toggleCursorHidden(e.NewValue); - return settings; - } - [BackgroundDependencyLoader] private void load() { - toggleHitMarkers(AnalysisSettings.HitMarkersEnabled.Value); - toggleAimMarkers(AnalysisSettings.AimMarkersEnabled.Value); - toggleAimLines(AnalysisSettings.AimLinesEnabled.Value); - toggleCursorHidden(AnalysisSettings.CursorHideEnabled.Value); + AddInternal(Settings = new OsuAnalysisSettings()); LoadReplay(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Settings.HitMarkersEnabled.BindValueChanged(e => toggleHitMarkers(e.NewValue), true); + Settings.AimMarkersEnabled.BindValueChanged(e => toggleAimMarkers(e.NewValue), true); + Settings.AimLinesEnabled.BindValueChanged(e => toggleAimLines(e.NewValue), true); + Settings.CursorHideEnabled.BindValueChanged(e => toggleCursorHidden(e.NewValue), true); + } + private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - private void toggleCursorHidden(bool value) => DrawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); + private void toggleCursorHidden(bool value) => drawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); protected void LoadReplay() { bool leftHeld = false; bool rightHeld = false; - foreach (var frame in Replay.Frames) + foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 6e11c87c3a8f..5419d4e17ae7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -9,13 +9,8 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisSettings : AnalysisSettings + public partial class OsuAnalysisSettings : PlayerSettingsGroup { - public OsuAnalysisSettings(Ruleset ruleset) - : base(ruleset) - { - } - [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -28,10 +23,18 @@ public OsuAnalysisSettings(Ruleset ruleset) [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); + public OsuAnalysisSettings() + : base("Analysis Settings") + { + } + [BackgroundDependencyLoader] private void load(IRulesetConfigCache cache) { - var config = (OsuRulesetConfigManager)cache.GetConfigFor(Ruleset)!; + AddRange(this.CreateSettingsControls()); + + var config = (OsuRulesetConfigManager)cache.GetConfigFor(new OsuRuleset())!; + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 2e48b8e16fcc..5af1fd386c3a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -406,7 +406,5 @@ public virtual IEnumerable CreateEditorSetupSections() => /// Can be overridden to avoid showing scroll speed changes in the editor. /// public virtual bool EditorShowScrollSpeed => true; - - public virtual DifficultySection? CreateEditorDifficultySection() => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs deleted file mode 100644 index b6c2a8c1c805..000000000000 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Containers; -using osu.Game.Replays; -using osu.Game.Screens.Play.PlayerSettings; - -namespace osu.Game.Rulesets.UI -{ - public abstract partial class AnalysisContainer : Container - { - protected Replay Replay; - protected DrawableRuleset DrawableRuleset; - - public AnalysisSettings AnalysisSettings; - - protected AnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - { - Replay = replay; - DrawableRuleset = drawableRuleset; - - AnalysisSettings = CreateAnalysisSettings(drawableRuleset.Ruleset); - } - - protected abstract AnalysisSettings CreateAnalysisSettings(Ruleset ruleset); - } -} diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5b1f59d549ad..a28b2716cb7b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -596,8 +596,6 @@ public HitWindows FirstAvailableHitWindows /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); - - public virtual AnalysisContainer CreateAnalysisContainer(Replay replay) => null; } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e116acdc1915..90a2f63faa8a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -291,12 +291,6 @@ protected override void Update() /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); - /// - /// Adds an analysis container to internal children for replays. - /// - /// - public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer); - #region Pooling support private readonly Dictionary pools = new Dictionary(); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs deleted file mode 100644 index 4c64eef92f9d..000000000000 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Configuration; -using osu.Game.Rulesets; - -namespace osu.Game.Screens.Play.PlayerSettings -{ - public partial class AnalysisSettings : PlayerSettingsGroup - { - protected Ruleset Ruleset; - - public AnalysisSettings(Ruleset ruleset) - : base("Analysis Settings") - { - Ruleset = ruleset; - - AddRange(this.CreateSettingsControls()); - } - } -} diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 82a2f0925040..ff60dbc0d0d1 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -71,14 +71,6 @@ private void load(OsuConfigManager config) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - - var analysisContainer = DrawableRuleset.CreateAnalysisContainer(GameplayState.Score.Replay); - - if (analysisContainer != null) - { - HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisContainer.AnalysisSettings); - DrawableRuleset.Playfield.AddAnalysisContainer(analysisContainer); - } } protected override void PrepareReplay() From 992a0da95727c3d574289d6b3664d2be0729166c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:43:33 +0900 Subject: [PATCH 22/43] Rename classes slightly --- .../TestSceneOsuAnalysisContainer.cs | 8 ++++---- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- .../{OsuAnalysisContainer.cs => ReplayAnalysisOverlay.cs} | 8 ++++---- .../{OsuAnalysisSettings.cs => ReplayAnalysisSettings.cs} | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game.Rulesets.Osu/UI/{OsuAnalysisContainer.cs => ReplayAnalysisOverlay.cs} (96%) rename osu.Game.Rulesets.Osu/UI/{OsuAnalysisSettings.cs => ReplayAnalysisSettings.cs} (93%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 536de8b41ac0..548e40487b42 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneOsuAnalysisContainer : OsuTestScene { - private TestOsuAnalysisContainer analysisContainer = null!; + private TestReplayAnalysisOverlay analysisContainer = null!; [SetUpSteps] public void SetUpSteps() @@ -32,7 +32,7 @@ public void SetUpSteps() Children = new Drawable[] { drawableRuleset, - analysisContainer = new TestOsuAnalysisContainer(fabricateReplay(), drawableRuleset), + analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay(), drawableRuleset), }; }); } @@ -96,9 +96,9 @@ private Replay fabricateReplay() return new Replay { Frames = frames }; } - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + private partial class TestReplayAnalysisOverlay : ReplayAnalysisOverlay { - public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) : base(replay, drawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ba0768db5dea..ee16fa91f41e 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -39,7 +39,7 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList protected override void LoadComplete() { if (HasReplayLoaded.Value) - LoadComponentAsync(new OsuAnalysisContainer(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); + LoadComponentAsync(new ReplayAnalysisOverlay(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); base.LoadComplete(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs similarity index 96% rename from osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 19e53944c964..521f67a77cd2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -19,18 +19,18 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisContainer : CompositeDrawable + public partial class ReplayAnalysisOverlay : CompositeDrawable { protected readonly HitMarkersContainer HitMarkers; protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - public OsuAnalysisSettings Settings = null!; + public ReplayAnalysisSettings Settings = null!; private readonly Replay replay; private readonly DrawableRuleset drawableRuleset; - public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) { this.replay = replay; this.drawableRuleset = drawableRuleset; @@ -46,7 +46,7 @@ public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) [BackgroundDependencyLoader] private void load() { - AddInternal(Settings = new OsuAnalysisSettings()); + AddInternal(Settings = new ReplayAnalysisSettings()); LoadReplay(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs similarity index 93% rename from osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 5419d4e17ae7..87180e155b37 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisSettings : PlayerSettingsGroup + public partial class ReplayAnalysisSettings : PlayerSettingsGroup { [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,7 +23,7 @@ public partial class OsuAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); - public OsuAnalysisSettings() + public ReplayAnalysisSettings() : base("Analysis Settings") { } From cc3d220f6f421603fda86800025fe98a859c7c8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:58:19 +0900 Subject: [PATCH 23/43] Allow settings to be added to replay HUD from ruleset --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 16 +++++++--------- .../UI/ReplayAnalysisOverlay.cs | 5 +++-- osu.Game/Screens/Play/ReplayPlayer.cs | 10 ++++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ee16fa91f41e..880b2dbe6f35 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -31,20 +30,19 @@ public partial class DrawableOsuRuleset : DrawableRuleset public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; - public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(ReplayPlayer? replayPlayer) { - if (HasReplayLoaded.Value) - LoadComponentAsync(new ReplayAnalysisOverlay(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); - - base.LoadComplete(); + if (replayPlayer != null) + PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay, this)); } - public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; + public override DrawableHitObject? CreateDrawableRepresentation(OsuHitObject h) => null; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 521f67a77cd2..398ab54981c9 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -44,9 +45,9 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load() + private void load(ReplayPlayer replayPlayer) { - AddInternal(Settings = new ReplayAnalysisSettings()); + replayPlayer.AddSettings(Settings = new ReplayAnalysisSettings()); LoadReplay(); } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index ff60dbc0d0d1..0c125264a1a1 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -55,6 +55,16 @@ public ReplayPlayer(Func, Score> createScore, Playe this.createScore = createScore; } + /// + /// Add a settings group to the HUD overlay. Intended to be used by rulesets to add replay-specific settings. + /// + /// The settings group to be shown. + public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => + { + settings.Expanded.Value = false; + HUDOverlay.PlayerSettingsOverlay.Add(settings); + }); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { From 9b81deb3ac95988d72afb96b509eb6785855281d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:09:23 +0900 Subject: [PATCH 24/43] Fix settings not working if `ReplayPlayer` is not available --- .../TestSceneOsuAnalysisContainer.cs | 2 ++ .../UI/ReplayAnalysisOverlay.cs | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 548e40487b42..fc2255a9cf3a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -103,6 +103,8 @@ public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) { } + public new ReplayAnalysisSettings Settings => base.Settings; + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 398ab54981c9..d16f1613703c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -26,7 +26,7 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - public ReplayAnalysisSettings Settings = null!; + protected ReplayAnalysisSettings Settings = null!; private readonly Replay replay; private readonly DrawableRuleset drawableRuleset; @@ -45,32 +45,30 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load(ReplayPlayer replayPlayer) + private void load(ReplayPlayer? replayPlayer) { - replayPlayer.AddSettings(Settings = new ReplayAnalysisSettings()); + Settings = new ReplayAnalysisSettings(); - LoadReplay(); + if (replayPlayer != null) + replayPlayer.AddSettings(Settings); + else + // only in test + AddInternal(Settings); + + loadReplay(); } protected override void LoadComplete() { base.LoadComplete(); - Settings.HitMarkersEnabled.BindValueChanged(e => toggleHitMarkers(e.NewValue), true); - Settings.AimMarkersEnabled.BindValueChanged(e => toggleAimMarkers(e.NewValue), true); - Settings.AimLinesEnabled.BindValueChanged(e => toggleAimLines(e.NewValue), true); - Settings.CursorHideEnabled.BindValueChanged(e => toggleCursorHidden(e.NewValue), true); + Settings.HitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.AimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.AimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.CursorHideEnabled.BindValueChanged(enabled => drawableRuleset.Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } - private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); - - private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); - - private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - - private void toggleCursorHidden(bool value) => drawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); - - protected void LoadReplay() + private void loadReplay() { bool leftHeld = false; bool rightHeld = false; From 6c07b873af174461888006d87420264b8f349109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:28:07 +0900 Subject: [PATCH 25/43] Isolate configuration container from analysis overlay --- .../TestSceneOsuAnalysisContainer.cs | 32 ++++++++--------- .../Configuration/OsuRulesetConfigManager.cs | 4 +-- .../UI/DrawableOsuRuleset.cs | 12 +++++-- .../UI/ReplayAnalysisOverlay.cs | 35 ++++++++----------- .../UI/ReplayAnalysisSettings.cs | 9 ++--- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fc2255a9cf3a..04fcb36cad7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -5,14 +5,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Replays; -using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; using osuTK; @@ -21,18 +21,20 @@ namespace osu.Game.Rulesets.Osu.Tests public partial class TestSceneOsuAnalysisContainer : OsuTestScene { private TestReplayAnalysisOverlay analysisContainer = null!; + private ReplayAnalysisSettings settings = null!; + + [Cached] + private OsuRulesetConfigManager config = new OsuRulesetConfigManager(null, new OsuRuleset().RulesetInfo); [SetUpSteps] public void SetUpSteps() { AddStep("create analysis container", () => { - DrawableOsuRuleset drawableRuleset = new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()); - Children = new Drawable[] { - drawableRuleset, - analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay(), drawableRuleset), + analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + settings = new ReplayAnalysisSettings(config), }; }); } @@ -40,27 +42,27 @@ public void SetUpSteps() [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => settings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => settings.HitMarkersEnabled.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => settings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => settings.AimMarkersEnabled.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => settings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => settings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } @@ -98,13 +100,11 @@ private Replay fabricateReplay() private partial class TestReplayAnalysisOverlay : ReplayAnalysisOverlay { - public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) + public TestReplayAnalysisOverlay(Replay replay) + : base(replay) { } - public new ReplayAnalysisSettings Settings => base.Settings; - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 23b7b9c1faa2..df5cd55c33fd 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.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. -#nullable disable - using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.UI; @@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Configuration { public class OsuRulesetConfigManager : RulesetConfigManager { - public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + public OsuRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 880b2dbe6f35..09dcd54c3cc3 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class DrawableOsuRuleset : DrawableRuleset { - protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + private Bindable? cursorHideEnabled; public new OsuInputManager KeyBindingInputManager => (OsuInputManager)base.KeyBindingInputManager; @@ -39,7 +41,13 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? private void load(ReplayPlayer? replayPlayer) { if (replayPlayer != null) - PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay, this)); + { + PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); + replayPlayer.AddSettings(new ReplayAnalysisSettings((OsuRulesetConfigManager)Config)); + + cursorHideEnabled = ((OsuRulesetConfigManager)Config).GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); + } } public override DrawableHitObject? CreateDrawableRepresentation(OsuHitObject h) => null; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index d16f1613703c..80fb561ed104 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; @@ -11,10 +12,9 @@ using osu.Framework.Graphics.Pooling; using osu.Game.Replays; using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisOverlay : CompositeDrawable { + private BindableBool hitMarkersEnabled { get; } = new BindableBool(); + private BindableBool aimMarkersEnabled { get; } = new BindableBool(); + private BindableBool aimLinesEnabled { get; } = new BindableBool(); + protected readonly HitMarkersContainer HitMarkers; protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - protected ReplayAnalysisSettings Settings = null!; - private readonly Replay replay; - private readonly DrawableRuleset drawableRuleset; - public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) + public ReplayAnalysisOverlay(Replay replay) { this.replay = replay; - this.drawableRuleset = drawableRuleset; InternalChildren = new Drawable[] { @@ -45,27 +45,22 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load(ReplayPlayer? replayPlayer) + private void load(OsuRulesetConfigManager config) { - Settings = new ReplayAnalysisSettings(); - - if (replayPlayer != null) - replayPlayer.AddSettings(Settings); - else - // only in test - AddInternal(Settings); - loadReplay(); + + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, hitMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, aimMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, aimLinesEnabled); } protected override void LoadComplete() { base.LoadComplete(); - Settings.HitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.AimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.AimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.CursorHideEnabled.BindValueChanged(enabled => drawableRuleset.Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); + hitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 87180e155b37..dd09ee146b21 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisSettings : PlayerSettingsGroup { + private readonly OsuRulesetConfigManager config; + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,18 +25,17 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); - public ReplayAnalysisSettings() + public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") { + this.config = config; } [BackgroundDependencyLoader] - private void load(IRulesetConfigCache cache) + private void load() { AddRange(this.CreateSettingsControls()); - var config = (OsuRulesetConfigManager)cache.GetConfigFor(new OsuRuleset())!; - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); From 6a309725ed62370accbf45784d15a57af52c00d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:50:45 +0900 Subject: [PATCH 26/43] Make test more usable --- .../TestSceneOsuAnalysisContainer.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 04fcb36cad7e..d31b27e00dba 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -33,9 +33,27 @@ public void SetUpSteps() { Children = new Drawable[] { - analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + new OsuPlayfieldAdjustmentContainer + { + Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + }, settings = new ReplayAnalysisSettings(config), }; + + settings.HitMarkersEnabled.Value = false; + settings.AimMarkersEnabled.Value = false; + settings.AimLinesEnabled.Value = false; + }); + } + + [Test] + public void TestEverythingOn() + { + AddStep("enable everything", () => + { + settings.HitMarkersEnabled.Value = true; + settings.AimMarkersEnabled.Value = true; + settings.AimLinesEnabled.Value = true; }); } @@ -76,8 +94,8 @@ private Replay fabricateReplay() for (int i = 0; i < 1000; i++) { - posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); - posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); + posX = Math.Clamp(posX + random.Next(-20, 21), 0, 500); + posY = Math.Clamp(posY + random.Next(-20, 21), 0, 500); var actions = new List(); From dcb463acafddefe68c2ed90fab193f599c9458f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:07:31 +0900 Subject: [PATCH 27/43] Split out classes and avoid weird configuration stuff --- .../Skinning/Default/HitMarker.cs | 74 -------- .../UI/ReplayAnalysis/AimLinesContainer.cs | 70 ++++++++ .../UI/ReplayAnalysis/AimMarkersContainer.cs | 20 +++ .../UI/ReplayAnalysis/AimPointEntry.cs | 20 +++ .../UI/ReplayAnalysis/HitMarker.cs | 27 +++ .../UI/ReplayAnalysis/HitMarkerEntry.cs | 18 ++ .../UI/ReplayAnalysis/HitMarkerLeftClick.cs | 49 ++++++ .../UI/ReplayAnalysis/HitMarkerMovement.cs | 50 ++++++ .../UI/ReplayAnalysis/HitMarkerRightClick.cs | 49 ++++++ .../UI/ReplayAnalysis/HitMarkersContainer.cs | 28 +++ .../UI/ReplayAnalysisOverlay.cs | 160 +----------------- 11 files changed, 334 insertions(+), 231 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs deleted file mode 100644 index fe662470bcd0..000000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public partial class HitMarker : CompositeDrawable - { - public HitMarker(OsuAction? action) - { - var (colour, length, hasBorder) = getConfig(action); - - if (hasBorder) - { - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - } - }; - } - - AddRangeInternal(new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - Colour = colour - } - }); - } - - private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) - { - switch (action) - { - case OsuAction.LeftButton: - return (Colour4.Orange, 20, true); - - case OsuAction.RightButton: - return (Colour4.LightGreen, 20, true); - - default: - return (Colour4.Gray.Opacity(0.5F), 8, false); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs new file mode 100644 index 000000000000..db747e277506 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs @@ -0,0 +1,70 @@ +// 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.Collections.Generic; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Performance; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimLinesContainer : Path + { + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + + public AimLinesContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + + PathRadius = 1f; + Colour = new Color4(255, 255, 255, 127); + } + + protected override void Update() + { + base.Update(); + + lifetimeManager.Update(Time.Current); + } + + public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + + public void Clear() => lifetimeManager.ClearEntries(); + + private void entryBecameAlive(LifetimeEntry entry) + { + aliveEntries.Add((AimPointEntry)entry); + updateVertices(); + } + + private void entryBecameDead(LifetimeEntry entry) + { + aliveEntries.Remove((AimPointEntry)entry); + updateVertices(); + } + + private void updateVertices() + { + ClearVertices(); + + foreach (var entry in aliveEntries) + { + AddVertex(entry.Position); + } + } + + private sealed class AimLinePointComparator : IComparer + { + public int Compare(AimPointEntry? x, AimPointEntry? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return x.LifetimeStart.CompareTo(y.LifetimeStart); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs new file mode 100644 index 000000000000..76e88ad5b0f1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs @@ -0,0 +1,20 @@ +// 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.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool pool; + + public AimMarkersContainer() + { + AddInternal(pool = new DrawablePool(80)); + } + + protected override HitMarker GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs new file mode 100644 index 000000000000..948568eb1286 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs @@ -0,0 +1,20 @@ +// 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.Performance; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimPointEntry : LifetimeEntry + { + public Vector2 Position { get; } + + public AimPointEntry(double time, Vector2 position) + { + LifetimeStart = time; + LifetimeEnd = time + 1_000; + Position = position; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs new file mode 100644 index 000000000000..339bdae5dae5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -0,0 +1,27 @@ +// 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.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarker : PoolableDrawableWithLifetime + { + public HitMarker() + { + Origin = Anchor.Centre; + } + + protected override void OnApply(AimPointEntry entry) + { + Position = entry.Position; + + using (BeginAbsoluteSequence(LifetimeStart)) + Show(); + + using (BeginAbsoluteSequence(LifetimeEnd - 200)) + this.FadeOut(200); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs new file mode 100644 index 000000000000..80c268910d02 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerEntry : AimPointEntry + { + public bool IsLeftMarker { get; } + + public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) + : base(lifetimeStart, position) + { + IsLeftMarker = isLeftMarker; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs new file mode 100644 index 000000000000..988fc283714a --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs @@ -0,0 +1,49 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerLeftClick : HitMarker + { + public HitMarkerLeftClick() + { + const float length = 20; + + Colour = Color4.OrangeRed; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs new file mode 100644 index 000000000000..4f9b6f879069 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -0,0 +1,50 @@ +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerMovement : HitMarker + { + public HitMarkerMovement() + { + const float length = 5; + + Colour = Color4.Gray.Opacity(0.4f); + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs new file mode 100644 index 000000000000..32cdd2d0b51f --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs @@ -0,0 +1,49 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerRightClick : HitMarker + { + public HitMarkerRightClick() + { + const float length = 20; + + Colour = Color4.GreenYellow; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs new file mode 100644 index 000000000000..fcc5fa28c219 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs @@ -0,0 +1,28 @@ +// 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.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool leftPool; + private readonly DrawablePool rightPool; + + public HitMarkersContainer() + { + AddInternal(leftPool = new DrawablePool(15)); + AddInternal(rightPool = new DrawablePool(15)); + } + + protected override HitMarker GetDrawable(HitMarkerEntry entry) + { + if (entry.IsLeftMarker) + return leftPool.Get(d => d.Apply(entry)); + + return rightPool.Get(d => d.Apply(entry)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 80fb561ed104..2cc194847422 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -1,22 +1,14 @@ // 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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Performance; -using osu.Framework.Graphics.Pooling; using osu.Game.Replays; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Skinning.Default; -using osuTK; -using osuTK.Graphics; +using osu.Game.Rulesets.Osu.UI.ReplayAnalysis; namespace osu.Game.Rulesets.Osu.UI { @@ -34,6 +26,8 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable public ReplayAnalysisOverlay(Replay replay) { + RelativeSizeAxes = Axes.Both; + this.replay = replay; InternalChildren = new Drawable[] @@ -95,153 +89,5 @@ private void loadReplay() } } } - - protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer - { - private readonly HitMarkerPool leftPool; - private readonly HitMarkerPool rightPool; - - public HitMarkersContainer() - { - AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15)); - AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15)); - } - - protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); - } - - protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer - { - private readonly HitMarkerPool pool; - - public AimMarkersContainer() - { - AddInternal(pool = new HitMarkerPool(null, 80)); - } - - protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); - } - - protected partial class AimLinesContainer : Path - { - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - - public AimLinesContainer() - { - lifetimeManager.EntryBecameAlive += entryBecameAlive; - lifetimeManager.EntryBecameDead += entryBecameDead; - - PathRadius = 1f; - Colour = new Color4(255, 255, 255, 127); - } - - protected override void Update() - { - base.Update(); - - lifetimeManager.Update(Time.Current); - } - - public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); - - public void Clear() => lifetimeManager.ClearEntries(); - - private void entryBecameAlive(LifetimeEntry entry) - { - aliveEntries.Add((AimPointEntry)entry); - updateVertices(); - } - - private void entryBecameDead(LifetimeEntry entry) - { - aliveEntries.Remove((AimPointEntry)entry); - updateVertices(); - } - - private void updateVertices() - { - ClearVertices(); - - foreach (var entry in aliveEntries) - { - AddVertex(entry.Position); - } - } - - private sealed class AimLinePointComparator : IComparer - { - public int Compare(AimPointEntry? x, AimPointEntry? y) - { - ArgumentNullException.ThrowIfNull(x); - ArgumentNullException.ThrowIfNull(y); - - return x.LifetimeStart.CompareTo(y.LifetimeStart); - } - } - } - - protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime - { - /// - /// This constructor only exists to meet the new() type constraint of . - /// - public HitMarkerDrawable() - { - } - - public HitMarkerDrawable(OsuAction? action) - { - Origin = Anchor.Centre; - InternalChild = new HitMarker(action); - } - - protected override void OnApply(AimPointEntry entry) - { - Position = entry.Position; - - using (BeginAbsoluteSequence(LifetimeStart)) - Show(); - - using (BeginAbsoluteSequence(LifetimeEnd - 200)) - this.FadeOut(200); - } - } - - protected partial class HitMarkerPool : DrawablePool - { - private readonly OsuAction? action; - - public HitMarkerPool(OsuAction? action, int initialSize) - : base(initialSize) - { - this.action = action; - } - - protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action); - } - - protected partial class AimPointEntry : LifetimeEntry - { - public Vector2 Position { get; } - - public AimPointEntry(double time, Vector2 position) - { - LifetimeStart = time; - LifetimeEnd = time + 1_000; - Position = position; - } - } - - protected partial class HitMarkerEntry : AimPointEntry - { - public bool IsLeftMarker { get; } - - public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) - : base(lifetimeStart, position) - { - IsLeftMarker = isLeftMarker; - } - } } } From 7f9a98a7aa01307c15ee48d3d74408482b58d6a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:17:22 +0900 Subject: [PATCH 28/43] More renames --- .../TestSceneOsuAnalysisContainer.cs | 6 ++--- ...rsContainer.cs => ClickMarkerContainer.cs} | 4 +-- ...ontainer.cs => MovementMarkerContainer.cs} | 4 +-- ...sContainer.cs => MovementPathContainer.cs} | 6 ++--- .../UI/ReplayAnalysisOverlay.cs | 26 +++++++++---------- 5 files changed, 22 insertions(+), 24 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkersContainer.cs => ClickMarkerContainer.cs} (85%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimMarkersContainer.cs => MovementMarkerContainer.cs} (78%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimLinesContainer.cs => MovementPathContainer.cs} (92%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index d31b27e00dba..fb6d59ddbaa9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -123,9 +123,9 @@ public TestReplayAnalysisOverlay(Replay replay) { } - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); + public bool AimMarkersVisible => MovementMarkers.Alpha > 0 && MovementMarkers.Entries.Any(); + public bool AimLinesVisible => MovementPath.Alpha > 0 && MovementPath.Vertices.Count > 1; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs similarity index 85% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index fcc5fa28c219..be56e26caf64 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,12 +6,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool leftPool; private readonly DrawablePool rightPool; - public HitMarkersContainer() + public ClickMarkerContainer() { AddInternal(leftPool = new DrawablePool(15)); AddInternal(rightPool = new DrawablePool(15)); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs similarity index 78% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs index 76e88ad5b0f1..8c2bdd590804 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs @@ -6,11 +6,11 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool pool; - public AimMarkersContainer() + public MovementMarkerContainer() { AddInternal(pool = new DrawablePool(80)); } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs similarity index 92% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index db747e277506..58c5993f349f 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -9,12 +9,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimLinesContainer : Path + public partial class MovementPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - public AimLinesContainer() + public MovementPathContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; @@ -32,8 +32,6 @@ protected override void Update() public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); - public void Clear() => lifetimeManager.ClearEntries(); - private void entryBecameAlive(LifetimeEntry entry) { aliveEntries.Add((AimPointEntry)entry); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 2cc194847422..d33d468c7e3b 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -18,9 +18,9 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool aimMarkersEnabled { get; } = new BindableBool(); private BindableBool aimLinesEnabled { get; } = new BindableBool(); - protected readonly HitMarkersContainer HitMarkers; - protected readonly AimMarkersContainer AimMarkers; - protected readonly AimLinesContainer AimLines; + protected readonly ClickMarkerContainer ClickMarkers; + protected readonly MovementMarkerContainer MovementMarkers; + protected readonly MovementPathContainer MovementPath; private readonly Replay replay; @@ -32,9 +32,9 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - HitMarkers = new HitMarkersContainer(), - AimLines = new AimLinesContainer(), - AimMarkers = new AimMarkersContainer(), + ClickMarkers = new ClickMarkerContainer(), + MovementPath = new MovementPathContainer(), + MovementMarkers = new MovementMarkerContainer(), }; } @@ -52,9 +52,9 @@ protected override void LoadComplete() { base.LoadComplete(); - hitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); + hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => MovementMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => MovementPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() @@ -66,8 +66,8 @@ private void loadReplay() { var osuFrame = (OsuReplayFrame)frame; - AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementPath.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,7 +76,7 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); leftHeld = true; } @@ -84,7 +84,7 @@ private void loadReplay() rightHeld = false; else if (!rightHeld && rightButton) { - HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); rightHeld = true; } } From a4a37c5ba64773ef5cae4626ca76220ca66ce87d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:25:29 +0900 Subject: [PATCH 29/43] Simplify lifetime entries and stuff --- ...AimPointEntry.cs => AnalysisFrameEntry.cs} | 7 ++- .../UI/ReplayAnalysis/ClickMarkerContainer.cs | 16 ++---- .../UI/ReplayAnalysis/HitMarker.cs | 4 +- ...itMarkerLeftClick.cs => HitMarkerClick.cs} | 37 +++++++++++--- .../UI/ReplayAnalysis/HitMarkerEntry.cs | 18 ------- .../UI/ReplayAnalysis/HitMarkerRightClick.cs | 49 ------------------- .../ReplayAnalysis/MovementMarkerContainer.cs | 4 +- .../ReplayAnalysis/MovementPathContainer.cs | 22 ++++++--- .../UI/ReplayAnalysisOverlay.cs | 8 +-- 9 files changed, 61 insertions(+), 104 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimPointEntry.cs => AnalysisFrameEntry.cs} (66%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerLeftClick.cs => HitMarkerClick.cs} (55%) delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs similarity index 66% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index 948568eb1286..ca11e6aff18c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -6,15 +6,18 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimPointEntry : LifetimeEntry + public partial class AnalysisFrameEntry : LifetimeEntry { + public OsuAction? Action { get; } + public Vector2 Position { get; } - public AimPointEntry(double time, Vector2 position) + public AnalysisFrameEntry(double time, Vector2 position, OsuAction? action = null) { LifetimeStart = time; LifetimeEnd = time + 1_000; Position = position; + Action = action; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index be56e26caf64..3de1a70d7c84 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,23 +6,15 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { - private readonly DrawablePool leftPool; - private readonly DrawablePool rightPool; + private readonly DrawablePool clickMarkerPool; public ClickMarkerContainer() { - AddInternal(leftPool = new DrawablePool(15)); - AddInternal(rightPool = new DrawablePool(15)); + AddInternal(clickMarkerPool = new DrawablePool(30)); } - protected override HitMarker GetDrawable(HitMarkerEntry entry) - { - if (entry.IsLeftMarker) - return leftPool.Get(d => d.Apply(entry)); - - return rightPool.Get(d => d.Apply(entry)); - } + protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index 339bdae5dae5..e2ecba44fc5b 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -6,14 +6,14 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarker : PoolableDrawableWithLifetime + public partial class HitMarker : PoolableDrawableWithLifetime { public HitMarker() { Origin = Anchor.Centre; } - protected override void OnApply(AimPointEntry entry) + protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs similarity index 55% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index 988fc283714a..4652ea341654 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,17 +1,18 @@ +using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerLeftClick : HitMarker + public partial class HitMarkerClick : HitMarker { - public HitMarkerLeftClick() + public HitMarkerClick() { const float length = 20; - - Colour = Color4.OrangeRed; + const float border_size = 3; InternalChildren = new Drawable[] { @@ -19,14 +20,14 @@ public HitMarkerLeftClick() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), + Size = new Vector2(border_size, length + border_size), Colour = Colour4.Black.Opacity(0.5F) }, new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), + Size = new Vector2(border_size, length + border_size), Rotation = 90, Colour = Colour4.Black.Opacity(0.5F) }, @@ -45,5 +46,27 @@ public HitMarkerLeftClick() } }; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + switch (entry.Action) + { + case OsuAction.LeftButton: + Colour = colours.BlueLight; + break; + + case OsuAction.RightButton: + Colour = colours.YellowLight; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs deleted file mode 100644 index 80c268910d02..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class HitMarkerEntry : AimPointEntry - { - public bool IsLeftMarker { get; } - - public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) - : base(lifetimeStart, position) - { - IsLeftMarker = isLeftMarker; - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs deleted file mode 100644 index 32cdd2d0b51f..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs +++ /dev/null @@ -1,49 +0,0 @@ -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class HitMarkerRightClick : HitMarker - { - public HitMarkerRightClick() - { - const float length = 20; - - Colour = Color4.GreenYellow; - - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } - }; - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs index 8c2bdd590804..d52f54650d58 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool pool; @@ -15,6 +15,6 @@ public MovementMarkerContainer() AddInternal(pool = new DrawablePool(80)); } - protected override HitMarker GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index 58c5993f349f..1d5215703663 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -3,16 +3,17 @@ using System; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; -using osuTK.Graphics; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class MovementPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); public MovementPathContainer() { @@ -20,7 +21,12 @@ public MovementPathContainer() lifetimeManager.EntryBecameDead += entryBecameDead; PathRadius = 1f; - Colour = new Color4(255, 255, 255, 127); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Pink2; } protected override void Update() @@ -30,17 +36,17 @@ protected override void Update() lifetimeManager.Update(Time.Current); } - public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + public void Add(AnalysisFrameEntry entry) => lifetimeManager.AddEntry(entry); private void entryBecameAlive(LifetimeEntry entry) { - aliveEntries.Add((AimPointEntry)entry); + aliveEntries.Add((AnalysisFrameEntry)entry); updateVertices(); } private void entryBecameDead(LifetimeEntry entry) { - aliveEntries.Remove((AimPointEntry)entry); + aliveEntries.Remove((AnalysisFrameEntry)entry); updateVertices(); } @@ -54,9 +60,9 @@ private void updateVertices() } } - private sealed class AimLinePointComparator : IComparer + private sealed class AimLinePointComparator : IComparer { - public int Compare(AimPointEntry? x, AimPointEntry? y) + public int Compare(AnalysisFrameEntry? x, AnalysisFrameEntry? y) { ArgumentNullException.ThrowIfNull(x); ArgumentNullException.ThrowIfNull(y); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index d33d468c7e3b..a42625a9c4db 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -66,8 +66,8 @@ private void loadReplay() { var osuFrame = (OsuReplayFrame)frame; - MovementMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - MovementPath.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,7 +76,7 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); leftHeld = true; } @@ -84,7 +84,7 @@ private void loadReplay() rightHeld = false; else if (!rightHeld && rightButton) { - ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); rightHeld = true; } } From a6ed719454dc3c0c2784f49d4d8ef627e424e9e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 21:04:59 +0900 Subject: [PATCH 30/43] Visual design pass --- .../UI/ReplayAnalysis/HitMarker.cs | 20 ++++++ .../UI/ReplayAnalysis/HitMarkerClick.cs | 66 ++++--------------- .../UI/ReplayAnalysis/HitMarkerMovement.cs | 40 ++++------- .../ReplayAnalysis/MovementPathContainer.cs | 2 +- .../UI/ReplayAnalysisOverlay.cs | 17 +++-- 5 files changed, 61 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index e2ecba44fc5b..4fa992ff1537 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -1,7 +1,9 @@ // 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.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis @@ -13,6 +15,9 @@ public HitMarker() Origin = Anchor.Centre; } + [Resolved] + private OsuColour colours { get; set; } = null!; + protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; @@ -22,6 +27,21 @@ protected override void OnApply(AnalysisFrameEntry entry) using (BeginAbsoluteSequence(LifetimeEnd - 200)) this.FadeOut(200); + + switch (entry.Action) + { + case OsuAction.LeftButton: + Colour = colours.BlueLight; + break; + + case OsuAction.RightButton: + Colour = colours.YellowLight; + break; + + default: + Colour = colours.Pink2; + break; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index 4652ea341654..ba41de7caa42 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,9 +1,8 @@ -using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -11,62 +10,25 @@ public partial class HitMarkerClick : HitMarker { public HitMarkerClick() { - const float length = 20; - const float border_size = 3; - InternalChildren = new Drawable[] { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(border_size, length + border_size), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box + new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(border_size, length + border_size), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) + Size = new Vector2(15), + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }, }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } }; } - - [Resolved] - private OsuColour colours { get; set; } = null!; - - protected override void OnApply(AnalysisFrameEntry entry) - { - base.OnApply(entry); - - switch (entry.Action) - { - case OsuAction.LeftButton: - Colour = colours.BlueLight; - break; - - case OsuAction.RightButton: - Colour = colours.YellowLight; - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs index 4f9b6f879069..0cda732b39cf 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -1,8 +1,7 @@ -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -10,41 +9,30 @@ public partial class HitMarkerMovement : HitMarker { public HitMarkerMovement() { - const float length = 5; - - Colour = Color4.Gray.Opacity(0.4f); - InternalChildren = new Drawable[] { - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1.2f) }, - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) + RelativeSizeAxes = Axes.Both, }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } }; } + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + Size = new Vector2(entry.Action != null ? 4 : 3); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index 1d5215703663..ff662c4dfa61 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -20,7 +20,7 @@ public MovementPathContainer() lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; - PathRadius = 1f; + PathRadius = 0.5f; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index a42625a9c4db..682842c56f03 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -62,13 +62,12 @@ private void loadReplay() bool leftHeld = false; bool rightHeld = false; + OsuAction? lastAction = null; + foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; - MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); - MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); - bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,17 +75,25 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); leftHeld = true; + lastAction = OsuAction.LeftButton; + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); } if (rightHeld && !rightButton) rightHeld = false; else if (!rightHeld && rightButton) { - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); rightHeld = true; + lastAction = OsuAction.RightButton; + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); } + + if (!leftButton && !rightButton) + lastAction = null; + + MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } } From 21146c35015b2aa6bbd9e015b97313061090c75b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 21:16:59 +0900 Subject: [PATCH 31/43] Add back shadow cast --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 09dcd54c3cc3..c6ad10c06269 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -32,6 +32,8 @@ public partial class DrawableOsuRuleset : DrawableRuleset public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; + protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { @@ -43,9 +45,9 @@ private void load(ReplayPlayer? replayPlayer) if (replayPlayer != null) { PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); - replayPlayer.AddSettings(new ReplayAnalysisSettings((OsuRulesetConfigManager)Config)); + replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); - cursorHideEnabled = ((OsuRulesetConfigManager)Config).GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } } From 08ebc83a89d25ad869ef372e4735ebfe0dcaefaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 12:07:16 +0900 Subject: [PATCH 32/43] Fix path getting misaligned with negative position values --- .../UI/ReplayAnalysis/MovementPathContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index ff662c4dfa61..dbe87b7ea7de 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Game.Graphics; +using osuTK; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -54,10 +55,19 @@ private void updateVertices() { ClearVertices(); + Vector2 min = Vector2.Zero; + foreach (var entry in aliveEntries) { AddVertex(entry.Position); + if (entry.Position.X < min.X) + min.X = entry.Position.X; + + if (entry.Position.Y < min.Y) + min.Y = entry.Position.Y; } + + Position = min; } private sealed class AimLinePointComparator : IComparer From ee26ff2e2901bbb51cf477b6c1c3289dcc19b9f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 13:07:49 +0900 Subject: [PATCH 33/43] Add out-of-bounds tests to test case --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fb6d59ddbaa9..b2cb8c546879 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -94,8 +94,8 @@ private Replay fabricateReplay() for (int i = 0; i < 1000; i++) { - posX = Math.Clamp(posX + random.Next(-20, 21), 0, 500); - posY = Math.Clamp(posY + random.Next(-20, 21), 0, 500); + posX = Math.Clamp(posX + random.Next(-20, 21), -100, 600); + posY = Math.Clamp(posY + random.Next(-20, 21), -100, 600); var actions = new List(); From 2d198e57e1ee2c7f7e6f71009c384301c650317c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 14:02:05 +0900 Subject: [PATCH 34/43] Second visual design pass --- .../UI/ReplayAnalysis/HitMarker.cs | 32 ++--------- .../UI/ReplayAnalysis/HitMarkerClick.cs | 57 ++++++++++++++++++- .../UI/ReplayAnalysis/HitMarkerMovement.cs | 38 ++++++++++--- .../ReplayAnalysis/MovementPathContainer.cs | 2 + .../UI/ReplayAnalysisOverlay.cs | 2 +- 5 files changed, 93 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index 4fa992ff1537..2187fdfe5742 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -8,40 +8,20 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarker : PoolableDrawableWithLifetime + public abstract partial class HitMarker : PoolableDrawableWithLifetime { - public HitMarker() + [Resolved] + protected OsuColour Colours { get; private set; } = null!; + + [BackgroundDependencyLoader] + private void load() { Origin = Anchor.Centre; } - [Resolved] - private OsuColour colours { get; set; } = null!; - protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; - - using (BeginAbsoluteSequence(LifetimeStart)) - Show(); - - using (BeginAbsoluteSequence(LifetimeEnd - 200)) - this.FadeOut(200); - - switch (entry.Action) - { - case OsuAction.LeftButton: - Colour = colours.BlueLight; - break; - - case OsuAction.RightButton: - Colour = colours.YellowLight; - break; - - default: - Colour = colours.Pink2; - break; - } } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index ba41de7caa42..a1024d193008 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,3 +1,4 @@ +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,17 +9,30 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class HitMarkerClick : HitMarker { - public HitMarkerClick() + private Container clickDisplay = null!; + + [BackgroundDependencyLoader] + private void load() { InternalChildren = new Drawable[] { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.125f), + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = Colours.Gray5, + }, new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(15), + RelativeSizeAxes = Axes.Both, + Colour = Colours.Gray5, Masking = true, - BorderThickness = 2, + BorderThickness = 2.2f, BorderColour = Color4.White, Child = new Box { @@ -28,7 +42,44 @@ public HitMarkerClick() Alpha = 0, }, }, + clickDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Colour = Colours.Yellow, + Scale = new Vector2(0.95f), + Width = 0.5f, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Width = 2, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }, + } + } }; + + Size = new Vector2(16); + } + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + clickDisplay.Alpha = entry.Action != null ? 1 : 0; + clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs index 0cda732b39cf..b2bbb47c4b94 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -1,29 +1,47 @@ +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class HitMarkerMovement : HitMarker { - public HitMarkerMovement() + private Container clickDisplay = null!; + private Circle mainCircle = null!; + + [BackgroundDependencyLoader] + private void load() { InternalChildren = new Drawable[] { - new Circle + mainCircle = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = OsuColour.Gray(0.2f), RelativeSizeAxes = Axes.Both, - Size = new Vector2(1.2f) + Colour = Colours.Pink2, }, - new Circle + clickDisplay = new Container { + Colour = Colours.Yellow, + Scale = new Vector2(0.8f), Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Masking = true, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Width = 2, + Colour = Color4.White, + }, + }, }, }; } @@ -31,8 +49,12 @@ public HitMarkerMovement() protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); + Size = new Vector2(entry.Action != null ? 4 : 2.5f); + + mainCircle.Colour = entry.Action != null ? Colours.Gray4 : Colours.Pink2; - Size = new Vector2(entry.Action != null ? 4 : 3); + clickDisplay.Alpha = entry.Action != null ? 1 : 0; + clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index dbe87b7ea7de..2ffc6ffe4a90 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Game.Graphics; @@ -28,6 +29,7 @@ public MovementPathContainer() private void load(OsuColour colours) { Colour = colours.Pink2; + BackgroundColour = colours.Pink2.Opacity(0); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 682842c56f03..06a1930fa388 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -32,8 +32,8 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - ClickMarkers = new ClickMarkerContainer(), MovementPath = new MovementPathContainer(), + ClickMarkers = new ClickMarkerContainer(), MovementMarkers = new MovementMarkerContainer(), }; } From 4f719b9fec4973a62514c6c8a5619dbae3350203 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 14:28:20 +0900 Subject: [PATCH 35/43] One more rename pass --- .../TestSceneOsuAnalysisContainer.cs | 4 ++-- .../{HitMarker.cs => AnalysisMarker.cs} | 2 +- .../{HitMarkerClick.cs => ClickMarker.cs} | 5 ++++- .../UI/ReplayAnalysis/ClickMarkerContainer.cs | 8 ++++---- ...athContainer.cs => CursorPathContainer.cs} | 4 ++-- .../{HitMarkerMovement.cs => FrameMarker.cs} | 5 ++++- .../UI/ReplayAnalysis/FrameMarkerContainer.cs | 20 +++++++++++++++++++ .../ReplayAnalysis/MovementMarkerContainer.cs | 20 ------------------- .../UI/ReplayAnalysisOverlay.cs | 16 +++++++-------- 9 files changed, 45 insertions(+), 39 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarker.cs => AnalysisMarker.cs} (87%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerClick.cs => ClickMarker.cs} (92%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{MovementPathContainer.cs => CursorPathContainer.cs} (96%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerMovement.cs => FrameMarker.cs} (91%) create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index b2cb8c546879..bea4f430eabd 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -124,8 +124,8 @@ public TestReplayAnalysisOverlay(Replay replay) } public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); - public bool AimMarkersVisible => MovementMarkers.Alpha > 0 && MovementMarkers.Entries.Any(); - public bool AimLinesVisible => MovementPath.Alpha > 0 && MovementPath.Vertices.Count > 1; + public bool AimMarkersVisible => FrameMarkers.Alpha > 0 && FrameMarkers.Entries.Any(); + public bool AimLinesVisible => CursorPath.Alpha > 0 && CursorPath.Vertices.Count > 1; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs similarity index 87% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs index 2187fdfe5742..9b602c88a854 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public abstract partial class HitMarker : PoolableDrawableWithLifetime + public abstract partial class AnalysisMarker : PoolableDrawableWithLifetime { [Resolved] protected OsuColour Colours { get; private set; } = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs similarity index 92% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs index a1024d193008..5386a80d9db9 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs @@ -7,7 +7,10 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerClick : HitMarker + /// + /// A marker which shows one click, with visuals focusing on the button which was clicked and the precise location of the click. + /// + public partial class ClickMarker : AnalysisMarker { private Container clickDisplay = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index 3de1a70d7c84..ff9444952131 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,15 +6,15 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { - private readonly DrawablePool clickMarkerPool; + private readonly DrawablePool clickMarkerPool; public ClickMarkerContainer() { - AddInternal(clickMarkerPool = new DrawablePool(30)); + AddInternal(clickMarkerPool = new DrawablePool(30)); } - protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); + protected override AnalysisMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs similarity index 96% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs index 2ffc6ffe4a90..1951d467e230 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs @@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class MovementPathContainer : Path + public partial class CursorPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - public MovementPathContainer() + public CursorPathContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs similarity index 91% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs index b2bbb47c4b94..6d4430707516 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs @@ -7,7 +7,10 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerMovement : HitMarker + /// + /// A marker which shows one movement frame, include any buttons which are pressed. + /// + public partial class FrameMarker : AnalysisMarker { private Container clickDisplay = null!; private Circle mainCircle = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs new file mode 100644 index 000000000000..63aea259f7cf --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs @@ -0,0 +1,20 @@ +// 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.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class FrameMarkerContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool pool; + + public FrameMarkerContainer() + { + AddInternal(pool = new DrawablePool(80)); + } + + protected override AnalysisMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs deleted file mode 100644 index d52f54650d58..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Pooling; -using osu.Game.Rulesets.Objects.Pooling; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer - { - private readonly DrawablePool pool; - - public MovementMarkerContainer() - { - AddInternal(pool = new DrawablePool(80)); - } - - protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 06a1930fa388..6d52e3d97583 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -19,8 +19,8 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool aimLinesEnabled { get; } = new BindableBool(); protected readonly ClickMarkerContainer ClickMarkers; - protected readonly MovementMarkerContainer MovementMarkers; - protected readonly MovementPathContainer MovementPath; + protected readonly FrameMarkerContainer FrameMarkers; + protected readonly CursorPathContainer CursorPath; private readonly Replay replay; @@ -32,9 +32,9 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - MovementPath = new MovementPathContainer(), + CursorPath = new CursorPathContainer(), ClickMarkers = new ClickMarkerContainer(), - MovementMarkers = new MovementMarkerContainer(), + FrameMarkers = new FrameMarkerContainer(), }; } @@ -53,8 +53,8 @@ protected override void LoadComplete() base.LoadComplete(); hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => MovementMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => MovementPath.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() @@ -92,8 +92,8 @@ private void loadReplay() if (!leftButton && !rightButton) lastAction = null; - MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); - MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } } From 7390d89c75f45bc86b7e638ed3eddbae3d829ddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:12:02 +0900 Subject: [PATCH 36/43] Switch to using `CircularProgress` for more consistent sizing Also show both mouse buttons at once on frame markers. --- .../UI/ReplayAnalysis/AnalysisFrameEntry.cs | 4 +- .../UI/ReplayAnalysis/ClickMarker.cs | 52 +++++++++---------- .../UI/ReplayAnalysis/FrameMarker.cs | 48 +++++++++-------- .../UI/ReplayAnalysisOverlay.cs | 9 +--- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index ca11e6aff18c..d44def1b6746 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class AnalysisFrameEntry : LifetimeEntry { - public OsuAction? Action { get; } + public OsuAction[] Action { get; } public Vector2 Position { get; } - public AnalysisFrameEntry(double time, Vector2 position, OsuAction? action = null) + public AnalysisFrameEntry(double time, Vector2 position, params OsuAction[] action) { LifetimeStart = time; LifetimeEnd = time + 1_000; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs index 5386a80d9db9..9788ea1aa9bc 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs @@ -1,7 +1,12 @@ +// 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -12,7 +17,8 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis /// public partial class ClickMarker : AnalysisMarker { - private Container clickDisplay = null!; + private CircularProgress leftClickDisplay = null!; + private CircularProgress rightClickDisplay = null!; [BackgroundDependencyLoader] private void load() @@ -45,33 +51,27 @@ private void load() Alpha = 0, }, }, - clickDisplay = new Container + leftClickDisplay = new CircularProgress { - Anchor = Anchor.Centre, + Colour = Colours.Yellow, + Size = new Vector2(0.95f), + Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, + Rotation = 180, + Progress = 0.5f, + InnerRadius = 0.18f, RelativeSizeAxes = Axes.Both, + }, + rightClickDisplay = new CircularProgress + { Colour = Colours.Yellow, - Scale = new Vector2(0.95f), - Width = 0.5f, - Masking = true, - BorderThickness = 2, - BorderColour = Color4.White, - Child = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Width = 2, - Masking = true, - BorderThickness = 2, - BorderColour = Color4.White, - Child = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0, - }, - } - } + Size = new Vector2(0.95f), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Progress = 0.5f, + InnerRadius = 0.18f, + RelativeSizeAxes = Axes.Both, + }, }; Size = new Vector2(16); @@ -81,8 +81,8 @@ protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); - clickDisplay.Alpha = entry.Action != null ? 1 : 0; - clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; + leftClickDisplay.Alpha = entry.Action.Contains(OsuAction.LeftButton) ? 1 : 0; + rightClickDisplay.Alpha = entry.Action.Contains(OsuAction.RightButton) ? 1 : 0; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs index 6d4430707516..35ee1445683a 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs @@ -1,9 +1,12 @@ +// 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.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -12,7 +15,8 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis /// public partial class FrameMarker : AnalysisMarker { - private Container clickDisplay = null!; + private CircularProgress leftClickDisplay = null!; + private CircularProgress rightClickDisplay = null!; private Circle mainCircle = null!; [BackgroundDependencyLoader] @@ -27,24 +31,26 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = Colours.Pink2, }, - clickDisplay = new Container + leftClickDisplay = new CircularProgress { Colour = Colours.Yellow, - Scale = new Vector2(0.8f), - Anchor = Anchor.Centre, + Size = new Vector2(0.8f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Rotation = 180, + Progress = 0.5f, + InnerRadius = 0.5f, + RelativeSizeAxes = Axes.Both, + }, + rightClickDisplay = new CircularProgress + { + Colour = Colours.Yellow, + Size = new Vector2(0.8f), + Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Progress = 0.5f, + InnerRadius = 0.5f, RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Masking = true, - Children = new Drawable[] - { - new Circle - { - RelativeSizeAxes = Axes.Both, - Width = 2, - Colour = Color4.White, - }, - }, }, }; } @@ -52,12 +58,12 @@ private void load() protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); - Size = new Vector2(entry.Action != null ? 4 : 2.5f); + Size = new Vector2(entry.Action.Any() ? 4 : 2.5f); - mainCircle.Colour = entry.Action != null ? Colours.Gray4 : Colours.Pink2; + mainCircle.Colour = entry.Action.Any() ? Colours.Gray4 : Colours.Pink2; - clickDisplay.Alpha = entry.Action != null ? 1 : 0; - clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; + leftClickDisplay.Alpha = entry.Action.Contains(OsuAction.LeftButton) ? 1 : 0; + rightClickDisplay.Alpha = entry.Action.Contains(OsuAction.RightButton) ? 1 : 0; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 6d52e3d97583..eb1ef49e5d89 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -62,8 +62,6 @@ private void loadReplay() bool leftHeld = false; bool rightHeld = false; - OsuAction? lastAction = null; - foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; @@ -76,7 +74,6 @@ private void loadReplay() else if (!leftHeld && leftButton) { leftHeld = true; - lastAction = OsuAction.LeftButton; ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); } @@ -85,14 +82,10 @@ private void loadReplay() else if (!rightHeld && rightButton) { rightHeld = true; - lastAction = OsuAction.RightButton; ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); } - if (!leftButton && !rightButton) - lastAction = null; - - FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, osuFrame.Actions.ToArray())); CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } From 7983a765ab09c47fd0cf908aa6b758420e98f9ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:12:16 +0900 Subject: [PATCH 37/43] Update test scene to show more button holds (including both buttons sometimes) --- .../TestSceneOsuAnalysisContainer.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index bea4f430eabd..292ecb7fde49 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -90,26 +90,28 @@ private Replay fabricateReplay() var random = new Random(); int posX = 250; int posY = 250; - bool leftOrRight = false; + + var actions = new HashSet(); for (int i = 0; i < 1000; i++) { posX = Math.Clamp(posX + random.Next(-20, 21), -100, 600); posY = Math.Clamp(posY + random.Next(-20, 21), -100, 600); - var actions = new List(); - - if (i % 20 == 0) + if (random.NextDouble() > (actions.Count == 0 ? 0.9 : 0.95)) + { + actions.Add(random.NextDouble() > 0.5 ? OsuAction.LeftButton : OsuAction.RightButton); + } + else if (random.NextDouble() > 0.7) { - actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); - leftOrRight = !leftOrRight; + actions.Remove(random.NextDouble() > 0.5 ? OsuAction.LeftButton : OsuAction.RightButton); } frames.Add(new OsuReplayFrame { Time = Time.Current + i * 15, Position = new Vector2(posX, posY), - Actions = actions + Actions = actions.ToList(), }); } From 47a9b345ebc52cdecd8e772510014d372f9376c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:15:15 +0900 Subject: [PATCH 38/43] Rename config variables and setting strings --- .../TestSceneOsuAnalysisContainer.cs | 24 +++++++++---------- .../Configuration/OsuRulesetConfigManager.cs | 12 +++++----- .../UI/ReplayAnalysisOverlay.cs | 18 +++++++------- .../UI/ReplayAnalysisSettings.cs | 24 +++++++++---------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 292ecb7fde49..fb8ac81b2cc9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -40,9 +40,9 @@ public void SetUpSteps() settings = new ReplayAnalysisSettings(config), }; - settings.HitMarkersEnabled.Value = false; - settings.AimMarkersEnabled.Value = false; - settings.AimLinesEnabled.Value = false; + settings.ShowClickMarkers.Value = false; + settings.ShowAimMarkers.Value = false; + settings.ShowCursorPath.Value = false; }); } @@ -51,36 +51,36 @@ public void TestEverythingOn() { AddStep("enable everything", () => { - settings.HitMarkersEnabled.Value = true; - settings.AimMarkersEnabled.Value = true; - settings.AimLinesEnabled.Value = true; + settings.ShowClickMarkers.Value = true; + settings.ShowAimMarkers.Value = true; + settings.ShowCursorPath.Value = true; }); } [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => settings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => settings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => settings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => settings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => settings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => settings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index df5cd55c33fd..e6002523b165 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -23,9 +23,9 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ShowCursorRipples, false); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); - SetDefault(OsuRulesetSetting.ReplayHitMarkersEnabled, false); - SetDefault(OsuRulesetSetting.ReplayAimMarkersEnabled, false); - SetDefault(OsuRulesetSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuRulesetSetting.ReplayClickMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayFrameMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayCursorPathEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); } } @@ -39,9 +39,9 @@ public enum OsuRulesetSetting PlayfieldBorderStyle, // Replay - ReplayHitMarkersEnabled, - ReplayAimMarkersEnabled, - ReplayAimLinesEnabled, + ReplayClickMarkersEnabled, + ReplayFrameMarkersEnabled, + ReplayCursorPathEnabled, ReplayCursorHideEnabled, } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index eb1ef49e5d89..622c32c51e63 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisOverlay : CompositeDrawable { - private BindableBool hitMarkersEnabled { get; } = new BindableBool(); - private BindableBool aimMarkersEnabled { get; } = new BindableBool(); - private BindableBool aimLinesEnabled { get; } = new BindableBool(); + private BindableBool showClickMarkers { get; } = new BindableBool(); + private BindableBool showFrameMarkers { get; } = new BindableBool(); + private BindableBool showCursorPath { get; } = new BindableBool(); protected readonly ClickMarkerContainer ClickMarkers; protected readonly FrameMarkerContainer FrameMarkers; @@ -43,18 +43,18 @@ private void load(OsuRulesetConfigManager config) { loadReplay(); - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, hitMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, aimMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, aimLinesEnabled); + config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, showClickMarkers); + config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, showFrameMarkers); + config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, showCursorPath); } protected override void LoadComplete() { base.LoadComplete(); - hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); + showClickMarkers.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + showFrameMarkers.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + showCursorPath.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index dd09ee146b21..7daab681805c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -13,17 +13,17 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup { private readonly OsuRulesetConfigManager config; - [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool HitMarkersEnabled { get; } = new BindableBool(); + [SettingSource("Show click markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowClickMarkers { get; } = new BindableBool(); - [SettingSource("Aim markers", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool AimMarkersEnabled { get; } = new BindableBool(); + [SettingSource("Show frame markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowAimMarkers { get; } = new BindableBool(); - [SettingSource("Aim lines", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool AimLinesEnabled { get; } = new BindableBool(); + [SettingSource("Show cursor path", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowCursorPath { get; } = new BindableBool(); - [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool CursorHideEnabled { get; } = new BindableBool(); + [SettingSource("Hide gameplay cursor", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool HideSkinCursor { get; } = new BindableBool(); public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") @@ -36,10 +36,10 @@ private void load() { AddRange(this.CreateSettingsControls()); - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); - config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, CursorHideEnabled); + config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, ShowClickMarkers); + config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, ShowAimMarkers); + config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, ShowCursorPath); + config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, HideSkinCursor); } } } From 0f01a855afd2a4585065eb41f4ee1ab49cdbc0d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:30:42 +0900 Subject: [PATCH 39/43] Add note about cursor hiding being potentially flaky --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c6ad10c06269..16edc654a7dd 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -48,6 +48,9 @@ private void load(ReplayPlayer? replayPlayer) replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + + // I have little faith in this working (other things touch cursor visibility) but haven't broken it yet. + // Let's wait for someone to report an issue before spending too much time on it. cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } } From a1cf67be629e7f3c4dedaac9aadab6b3f0f73598 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:53:53 +0900 Subject: [PATCH 40/43] Add setting to adjust replay analysis display length --- .../Configuration/OsuRulesetConfigManager.cs | 2 + .../UI/ReplayAnalysis/AnalysisFrameEntry.cs | 4 +- .../UI/ReplayAnalysisOverlay.cs | 70 +++++++++++++------ .../UI/ReplayAnalysisSettings.cs | 10 +++ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index e6002523b165..8a8b78b64509 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -27,6 +27,7 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ReplayFrameMarkersEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorPathEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAnalysisDisplayLength, 750); } } @@ -43,5 +44,6 @@ public enum OsuRulesetSetting ReplayFrameMarkersEnabled, ReplayCursorPathEnabled, ReplayCursorHideEnabled, + ReplayAnalysisDisplayLength, } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index d44def1b6746..116bccc74705 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -12,10 +12,10 @@ public partial class AnalysisFrameEntry : LifetimeEntry public Vector2 Position { get; } - public AnalysisFrameEntry(double time, Vector2 position, params OsuAction[] action) + public AnalysisFrameEntry(double time, double displayLength, Vector2 position, params OsuAction[] action) { LifetimeStart = time; - LifetimeEnd = time + 1_000; + LifetimeEnd = time + displayLength; Position = position; Action = action; } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 622c32c51e63..0c257a68c55a 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -17,10 +17,11 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool showClickMarkers { get; } = new BindableBool(); private BindableBool showFrameMarkers { get; } = new BindableBool(); private BindableBool showCursorPath { get; } = new BindableBool(); + private BindableInt displayLength { get; } = new BindableInt(); - protected readonly ClickMarkerContainer ClickMarkers; - protected readonly FrameMarkerContainer FrameMarkers; - protected readonly CursorPathContainer CursorPath; + protected ClickMarkerContainer ClickMarkers = null!; + protected FrameMarkerContainer FrameMarkers = null!; + protected CursorPathContainer CursorPath = null!; private readonly Replay replay; @@ -29,36 +30,65 @@ public ReplayAnalysisOverlay(Replay replay) RelativeSizeAxes = Axes.Both; this.replay = replay; - - InternalChildren = new Drawable[] - { - CursorPath = new CursorPathContainer(), - ClickMarkers = new ClickMarkerContainer(), - FrameMarkers = new FrameMarkerContainer(), - }; } + private bool requireDisplay => showClickMarkers.Value || showFrameMarkers.Value || showCursorPath.Value; + [BackgroundDependencyLoader] private void load(OsuRulesetConfigManager config) { - loadReplay(); - config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, showClickMarkers); config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, showFrameMarkers); config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, showCursorPath); + config.BindWith(OsuRulesetSetting.ReplayAnalysisDisplayLength, displayLength); } protected override void LoadComplete() { base.LoadComplete(); - showClickMarkers.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - showFrameMarkers.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - showCursorPath.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); + showClickMarkers.BindValueChanged(enabled => + { + initialise(); + ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + showFrameMarkers.BindValueChanged(enabled => + { + initialise(); + FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + showCursorPath.BindValueChanged(enabled => + { + initialise(); + CursorPath.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + displayLength.BindValueChanged(_ => + { + isLoaded = false; + initialise(); + }, true); } - private void loadReplay() + private bool isLoaded; + + private void initialise() { + if (!requireDisplay) + return; + + if (isLoaded) + return; + + isLoaded = true; + + // It's faster to reinitialise the whole drawable stack than use `Clear` on `PooledDrawableWithLifetimeContainer` + InternalChildren = new Drawable[] + { + CursorPath = new CursorPathContainer(), + ClickMarkers = new ClickMarkerContainer(), + FrameMarkers = new FrameMarkerContainer(), + }; + bool leftHeld = false; bool rightHeld = false; @@ -74,7 +104,7 @@ private void loadReplay() else if (!leftHeld && leftButton) { leftHeld = true; - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, OsuAction.LeftButton)); } if (rightHeld && !rightButton) @@ -82,11 +112,11 @@ private void loadReplay() else if (!rightHeld && rightButton) { rightHeld = true; - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, OsuAction.RightButton)); } - FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, osuFrame.Actions.ToArray())); - CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, osuFrame.Actions.ToArray())); + CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position)); } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 7daab681805c..6acafb5d3bf5 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -25,6 +25,15 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide gameplay cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HideSkinCursor { get; } = new BindableBool(); + [SettingSource("Display length", SettingControlType = typeof(PlayerSliderBar))] + public BindableInt DisplayLength { get; } = new BindableInt + { + MinValue = 100, + Default = 800, + MaxValue = 2000, + Precision = 100, + }; + public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") { @@ -40,6 +49,7 @@ private void load() config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, ShowAimMarkers); config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, ShowCursorPath); config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, HideSkinCursor); + config.BindWith(OsuRulesetSetting.ReplayAnalysisDisplayLength, DisplayLength); } } } From 167e3a337796d925025cba8497300734d6f76a14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 16:01:28 +0900 Subject: [PATCH 41/43] Make loading asynchronous --- .../UI/ReplayAnalysisOverlay.cs | 59 +++++++++++-------- .../UI/ReplayAnalysisSettings.cs | 6 +- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 0c257a68c55a..8a48e811118e 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Replays; @@ -19,9 +21,9 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool showCursorPath { get; } = new BindableBool(); private BindableInt displayLength { get; } = new BindableInt(); - protected ClickMarkerContainer ClickMarkers = null!; - protected FrameMarkerContainer FrameMarkers = null!; - protected CursorPathContainer CursorPath = null!; + protected ClickMarkerContainer? ClickMarkers; + protected FrameMarkerContainer? FrameMarkers; + protected CursorPathContainer? CursorPath; private readonly Replay replay; @@ -47,42 +49,43 @@ protected override void LoadComplete() { base.LoadComplete(); - showClickMarkers.BindValueChanged(enabled => - { - initialise(); - ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0); - }, true); - showFrameMarkers.BindValueChanged(enabled => - { - initialise(); - FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0); - }, true); - showCursorPath.BindValueChanged(enabled => - { - initialise(); - CursorPath.FadeTo(enabled.NewValue ? 1 : 0); - }, true); displayLength.BindValueChanged(_ => { - isLoaded = false; - initialise(); + // Need to fully reload to make this work. + loaded.Invalidate(); }, true); } - private bool isLoaded; + private readonly Cached loaded = new Cached(); + + private CancellationTokenSource? generationCancellationSource; + + protected override void Update() + { + base.Update(); + + if (requireDisplay) + { + initialise(); + + if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; + if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; + if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; + } + } private void initialise() { - if (!requireDisplay) + if (loaded.IsValid) return; - if (isLoaded) - return; + loaded.Validate(); - isLoaded = true; + generationCancellationSource?.Cancel(); + generationCancellationSource = new CancellationTokenSource(); // It's faster to reinitialise the whole drawable stack than use `Clear` on `PooledDrawableWithLifetimeContainer` - InternalChildren = new Drawable[] + var newDrawables = new Drawable[] { CursorPath = new CursorPathContainer(), ClickMarkers = new ClickMarkerContainer(), @@ -92,6 +95,8 @@ private void initialise() bool leftHeld = false; bool rightHeld = false; + // This should probably be async as well, but it's a bit of a pain to debounce and everything. + // Let's address concerns when they are raised. foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; @@ -118,6 +123,8 @@ private void initialise() FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, osuFrame.Actions.ToArray())); CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position)); } + + LoadComponentsAsync(newDrawables, drawables => InternalChildrenEnumerable = drawables, generationCancellationSource.Token); } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 6acafb5d3bf5..dc4730d76ab5 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -28,10 +28,10 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Display length", SettingControlType = typeof(PlayerSliderBar))] public BindableInt DisplayLength { get; } = new BindableInt { - MinValue = 100, - Default = 800, + MinValue = 200, MaxValue = 2000, - Precision = 100, + Default = 800, + Precision = 200, }; public ReplayAnalysisSettings(OsuRulesetConfigManager config) From 7136483f85461c73d714a7588cb9f8a6a193632d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Sep 2024 09:45:34 +0200 Subject: [PATCH 42/43] Fix nullability inspections --- .../TestSceneOsuAnalysisContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fb8ac81b2cc9..d72a34767514 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -125,9 +125,9 @@ public TestReplayAnalysisOverlay(Replay replay) { } - public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); - public bool AimMarkersVisible => FrameMarkers.Alpha > 0 && FrameMarkers.Entries.Any(); - public bool AimLinesVisible => CursorPath.Alpha > 0 && CursorPath.Vertices.Count > 1; + public bool HitMarkersVisible => ClickMarkers?.Alpha > 0 && ClickMarkers.Entries.Any(); + public bool AimMarkersVisible => FrameMarkers?.Alpha > 0 && FrameMarkers.Entries.Any(); + public bool AimLinesVisible => CursorPath?.Alpha > 0 && CursorPath.Vertices.Count > 1; } } } From b9ddac420171e1ecfbcb4374538d8c1c117a92a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Sep 2024 09:45:37 +0200 Subject: [PATCH 43/43] Fix test failures --- .../TestSceneOsuAnalysisContainer.cs | 12 ++++++------ osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs | 8 +++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index d72a34767514..184938ceda8b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -61,27 +61,27 @@ public void TestEverythingOn() public void TestHitMarkers() { AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true); - AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddUntilStep("hit markers visible", () => analysisContainer.HitMarkersVisible); AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false); - AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + AddUntilStep("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true); - AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddUntilStep("aim markers visible", () => analysisContainer.AimMarkersVisible); AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false); - AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + AddUntilStep("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true); - AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddUntilStep("aim lines visible", () => analysisContainer.AimLinesVisible); AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false); - AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + AddUntilStep("aim lines not visible", () => !analysisContainer.AimLinesVisible); } private Replay fabricateReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 8a48e811118e..2b7f6c9fc96d 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -65,13 +65,11 @@ protected override void Update() base.Update(); if (requireDisplay) - { initialise(); - if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; - if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; - if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; - } + if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; + if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; + if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; } private void initialise()