diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs index 5ece5df66fb8..7f4fa2613723 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs @@ -52,7 +52,7 @@ private void onDirectionChanged() float hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? 0; float scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value ?? 0; - float hitPositionFromTop = 480f * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR - hitPosition; + float hitPositionFromTop = 480f * LegacySkin.POSITION_SCALE_FACTOR - hitPosition; if (scorePosition > hitPositionFromTop / 2f) { diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20260806.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20260806.osk new file mode 100644 index 000000000000..d90d6ba923f3 Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-classic-20260806.osk differ diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 995d4cf65f6c..6adbf8538efb 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -83,6 +83,8 @@ public class SkinDeserialisationTest "Archives/modified-classic-20250827.osk", // Covers "Argon" judgement counter "Archives/modified-argon-20250308.osk", + // Covers legacy bar hit error meter + "Archives/modified-classic-20260806.osk", }; /// diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayRulesetLayouts.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayRulesetLayouts.cs index 131b901d6f21..7a8950a4f1d7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayRulesetLayouts.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayRulesetLayouts.cs @@ -22,6 +22,7 @@ using osu.Game.Online.Spectator; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -101,6 +102,7 @@ public void TestLayout( (typeof(IGameplayClock), gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false)), (typeof(SpectatorClient), spectatorClient), (typeof(IGameplayLeaderboardProvider), new TestGameplayLeaderboardProvider()), + (typeof(DrawableRuleset), ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(ruleset.RulesetInfo).GetPlayableBeatmap(ruleset.RulesetInfo))) ]; if (drawableRuleset is IDrawableScrollingRuleset scrolling) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 24215ed92512..a71acb701501 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -247,6 +247,29 @@ private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) Rotation = 270, Margin = new MarginPadding { Left = 50 } }); + + Add(new LegacyBarHitErrorMeter + { + Anchor = Anchor.CentreRight, + Origin = Anchor.BottomCentre, + Rotation = 90, + X = -100, + }); + + Add(new LegacyBarHitErrorMeter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + Rotation = 90, + X = 100, + }); + + Add(new LegacyBarHitErrorMeter + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -100 + }); } private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/LegacyBarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/LegacyBarHitErrorMeter.cs new file mode 100644 index 000000000000..76e9a2e45cd1 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/LegacyBarHitErrorMeter.cs @@ -0,0 +1,152 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD.HitErrorMeters +{ + /// + public partial class LegacyBarHitErrorMeter : HitErrorMeter + { + private const float bar_height = 3; + + private (HitResult result, double length)[] hitWindows = null!; + private DrawablePool judgementLinePool = null!; + private Container judgementContainer = null!; + private Triangle arrow = null!; + + private double maxHitWindow = 1; + private float floatingError; + + [BackgroundDependencyLoader] + private void load() + { + Container colourBarContainer; + + hitWindows = HitWindows.GetAllAvailableWindows().Where(w => w.result.IsHit()).ToArray(); + + AutoSizeAxes = Axes.X; + Height = (bar_height * 4) * LegacySkin.POSITION_SCALE_FACTOR; + Origin = Anchor.Centre; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Black, + Alpha = 0.6f, + }, + colourBarContainer = new Container + { + AutoSizeAxes = Axes.X, + Height = bar_height * LegacySkin.POSITION_SCALE_FACTOR, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1.5f, bar_height * 4) * LegacySkin.POSITION_SCALE_FACTOR, + }, + judgementLinePool = new DrawablePool(50), + judgementContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + arrow = new Triangle + { + Size = new Vector2(17, 8) * 0.6f, + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + Scale = new Vector2(1, -1), + } + }; + + foreach (var hitWindow in hitWindows) + { + maxHitWindow = Math.Max(maxHitWindow, hitWindow.length); + + colourBarContainer.Add(new Box + { + Size = new Vector2((float)hitWindow.length, bar_height) * LegacySkin.POSITION_SCALE_FACTOR, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = GetColourForHitResult(hitWindow.result), + }); + } + } + + protected override void OnNewJudgement(JudgementResult judgement) + { + if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) + return; + + if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) + return; + + float relativePosition = getRelativeJudgementPosition(judgement.TimeOffset); + + judgementLinePool.Get(drawableJudgement => + { + drawableJudgement.X = relativePosition; + drawableJudgement.Colour = GetColourForHitResult(judgement.Type); + + judgementContainer.Add(drawableJudgement); + }); + + floatingError = floatingError * 0.8f + relativePosition * 0.2f; + arrow.MoveToX(floatingError, 800, Easing.Out); + } + + private float getRelativeJudgementPosition(double value) => Math.Clamp((float)(value / maxHitWindow) / 2, -0.5f, 0.5f); + + public override void Clear() + { + foreach (var j in judgementContainer) + { + j.ClearTransforms(); + j.Expire(); + } + } + + internal partial class JudgementLine : PoolableDrawable + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Y; + Width = 3; + RelativePositionAxes = Axes.X; + Blending = BlendingParameters.Additive; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + this.FadeTo(0.4f) + .Then() + .FadeOut(10000) + .Expire(); + } + } + } +} diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 58c6585e27b9..68b3a1483341 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -11,17 +11,12 @@ namespace osu.Game.Skinning { public class LegacyManiaSkinConfiguration : IHasCustomColours { - /// - /// Conversion factor from converting legacy positioning values (based in x480 dimensions) to x768. - /// - public const float POSITION_SCALE_FACTOR = 1.6f; - /// /// Size of a legacy column in the default skin, used for determining relative scale factors. /// - public const float DEFAULT_COLUMN_SIZE = 30 * POSITION_SCALE_FACTOR; + public const float DEFAULT_COLUMN_SIZE = 30 * LegacySkin.POSITION_SCALE_FACTOR; - public const float DEFAULT_HIT_POSITION = (480 - 402) * POSITION_SCALE_FACTOR; + public const float DEFAULT_HIT_POSITION = (480 - 402) * LegacySkin.POSITION_SCALE_FACTOR; public readonly int Keys; @@ -38,9 +33,9 @@ public class LegacyManiaSkinConfiguration : IHasCustomColours public readonly float[] HoldNoteLightWidth; public float HitPosition = DEFAULT_HIT_POSITION; - public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; - public float ComboPosition = 111 * POSITION_SCALE_FACTOR; - public float ScorePosition = 300 * POSITION_SCALE_FACTOR; + public float LightPosition = (480 - 413) * LegacySkin.POSITION_SCALE_FACTOR; + public float ComboPosition = 111 * LegacySkin.POSITION_SCALE_FACTOR; + public float ScorePosition = 300 * LegacySkin.POSITION_SCALE_FACTOR; public float BarLineHeight = 1.2f; public bool ShowJudgementLine = true; public bool KeysUnderNotes; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 94bd79fea3de..6f7634a04106 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -91,19 +91,19 @@ private void flushPendingLines() break; case "HitPosition": - currentConfig.HitPosition = (480 - Math.Clamp(float.Parse(pair.Value, CultureInfo.InvariantCulture), 240, 480)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.HitPosition = (480 - Math.Clamp(float.Parse(pair.Value, CultureInfo.InvariantCulture), 240, 480)) * LegacySkin.POSITION_SCALE_FACTOR; break; case "LightPosition": - currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacySkin.POSITION_SCALE_FACTOR; break; case "ComboPosition": - currentConfig.ComboPosition = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.ComboPosition = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacySkin.POSITION_SCALE_FACTOR; break; case "ScorePosition": - currentConfig.ScorePosition = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.ScorePosition = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacySkin.POSITION_SCALE_FACTOR; break; case "JudgementLine": @@ -128,7 +128,7 @@ private void flushPendingLines() break; case "WidthForNoteHeightScale": - currentConfig.WidthForNoteHeightScale = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.WidthForNoteHeightScale = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacySkin.POSITION_SCALE_FACTOR; break; case "LightFramePerSecond": @@ -209,7 +209,7 @@ private void parseArrayValue(string value, float[] output, bool applyScaleFactor parsedValue = 0; if (applyScaleFactor) - parsedValue *= LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + parsedValue *= LegacySkin.POSITION_SCALE_FACTOR; output[i] = parsedValue; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 219f909403f9..ed744891f9c8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -30,6 +30,11 @@ namespace osu.Game.Skinning { public class LegacySkin : Skin { + /// + /// Conversion factor from converting legacy positioning values (based in x480 dimensions) to x768. + /// + public const float POSITION_SCALE_FACTOR = 1.6f; + protected virtual bool AllowManiaConfigLookups => true; /// @@ -448,8 +453,7 @@ protected override void ParseConfigurationStream(Stream stream) if (hitError != null) { hitError.Anchor = Anchor.BottomCentre; - hitError.Origin = Anchor.CentreLeft; - hitError.Rotation = -90; + hitError.Origin = Anchor.BottomCentre; } foreach (var d in container.OfType()) @@ -461,7 +465,7 @@ protected override void ParseConfigurationStream(Stream stream) new LegacyScoreCounter(), new LegacyAccuracyCounter(), new LegacySongProgress(), - new BarHitErrorMeter(), + new LegacyBarHitErrorMeter(), // to match stable, health bars are in front of everything else // for the sake of hacky full screen area health bars diff --git a/osu.Game/Skinning/LegacySkinEncoder.cs b/osu.Game/Skinning/LegacySkinEncoder.cs index 326af9b991e3..9e5f6cff4f7c 100644 --- a/osu.Game/Skinning/LegacySkinEncoder.cs +++ b/osu.Game/Skinning/LegacySkinEncoder.cs @@ -110,10 +110,10 @@ public void Encode(TextWriter textWriter) writeValue(textWriter, @"JudgementLine", maniaConfig.ShowJudgementLine ? @"1" : @"0"); writeValue(textWriter, @"BarlineHeight", maniaConfig.BarLineHeight.ToString(CultureInfo.InvariantCulture), defaultValue: @"1.2"); - float hitPosition = 480 - (maniaConfig.HitPosition / LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR); + float hitPosition = 480 - (maniaConfig.HitPosition / LegacySkin.POSITION_SCALE_FACTOR); writeValue(textWriter, @"HitPosition", hitPosition.ToString(CultureInfo.InvariantCulture), defaultValue: @"402"); - float lightPosition = 480 - (maniaConfig.LightPosition / LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR); + float lightPosition = 480 - (maniaConfig.LightPosition / LegacySkin.POSITION_SCALE_FACTOR); writeValue(textWriter, @"LightPosition", lightPosition.ToString(CultureInfo.InvariantCulture), defaultValue: @"413"); writeValue(textWriter, @"ComboPosition", undoPositionScaleFactor(maniaConfig.ComboPosition).ToString(CultureInfo.InvariantCulture), defaultValue: @"111"); @@ -189,7 +189,7 @@ private void writeGenericColour(TextWriter textWriter, Dictionary f / LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + private float undoPositionScaleFactor(float f) => f / LegacySkin.POSITION_SCALE_FACTOR; private string enumerableToString(IEnumerable ts) where T : IFormattable