Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
6 changes: 4 additions & 2 deletions osu.Game.Tests/Skins/SkinDeserialisationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Skinning.Components;
using osu.Game.Skinning.Triangles;
using osu.Game.Tests.Resources;

namespace osu.Game.Tests.Skins
Expand Down Expand Up @@ -77,6 +77,8 @@ public class SkinDeserialisationTest
"Archives/modified-argon-20250214.osk",
// Covers skinnable leaderboard
"Archives/modified-argon-20250424.osk",
// Covers "Argon" unstable rate counter
"Archives/modified-argon-20250809.osk",
};

/// <summary>
Expand Down Expand Up @@ -170,7 +172,7 @@ public void TestDeserialiseModifiedClassic()
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(TrianglesUnstableRateCounter)));
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
}
Expand Down
55 changes: 23 additions & 32 deletions osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#nullable disable

using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
Expand All @@ -14,43 +16,50 @@
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osu.Game.Skinning.Triangles;

namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneUnstableRateCounter : OsuTestScene
public partial class TestSceneUnstableRateCounter : SkinnableHUDComponentTestScene
{
[Cached(typeof(ScoreProcessor))]
private TestScoreProcessor scoreProcessor = new TestScoreProcessor();

private readonly OsuHitWindows hitWindows;

private UnstableRateCounter counter;

private double prev;

protected override Drawable CreateDefaultImplementation() => new TrianglesUnstableRateCounter();
protected override Drawable CreateArgonImplementation() => new ArgonUnstableRateCounter();
protected override Drawable CreateLegacyImplementation() => Empty();

public TestSceneUnstableRateCounter()
{
hitWindows = new OsuHitWindows();
hitWindows.SetDifficulty(5);
}

[SetUpSteps]
public void SetUp()
public override void SetUpSteps()
{
AddStep("Reset Score Processor", () => scoreProcessor.Reset());
base.SetUpSteps();
}

[Test]
public void TestBasic()
public void TestDisplay()
{
AddStep("Create Display", recreateDisplay);
AddSliderStep("UR", 0, 2000, 0, v => this.ChildrenOfType<UnstableRateCounter>().ForEach(c => c.Current.Value = v));
AddToggleStep("toggle validity", v => this.ChildrenOfType<UnstableRateCounter>().ForEach(c => c.IsValid.Value = v));
}

[Test]
public void TestBasic()
{
// Needs multiples 2 by the nature of UR, and went for 4 to be safe.
// Creates a 250 UR by placing a +25ms then a -25ms judgement, which then results in a 250 UR
AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4);

AddUntilStep("UR = 250", () => counter.Current.Value == 250.0);
AddUntilStep("UR = 250", () => this.ChildrenOfType<UnstableRateCounter>().All(c => c.Current.Value == 250));

AddRepeatStep("Revert UR", () =>
{
Expand All @@ -63,8 +72,8 @@ public void TestBasic()
});
}, 4);

AddUntilStep("UR is 0", () => counter.Current.Value == 0.0);
AddUntilStep("Counter is invalid", () => counter.Child.Alpha == 0.3f);
AddUntilStep("UR is 0", () => this.ChildrenOfType<UnstableRateCounter>().All(c => c.Current.Value == 0));
AddUntilStep("Counter is invalid", () => this.ChildrenOfType<UnstableRateCounter>().All(c => !c.IsValid.Value));

//Sets a UR of 0 by creating 10 10ms offset judgements. Since average = offset, UR = 0
AddRepeatStep("Set UR to 0", () => applyJudgement(10, false), 10);
Expand All @@ -77,42 +86,24 @@ public void TestCounterReceivesJudgementsBeforeCreation()
{
AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4);

AddStep("Create Display", recreateDisplay);

AddUntilStep("UR = 250", () => counter.Current.Value == 250.0);
AddUntilStep("UR = 250", () => this.ChildrenOfType<UnstableRateCounter>().All(c => c.Current.Value == 250));
}

[Test]
public void TestStaticRateChange()
{
AddStep("Create Display", recreateDisplay);

AddRepeatStep("Set UR to 250 at 1.5x", () => applyJudgement(25, true, 1.5), 4);

AddUntilStep("UR = 250/1.5", () => counter.Current.Value == Math.Round(250.0 / 1.5));
AddUntilStep("UR = 250/1.5", () => this.ChildrenOfType<UnstableRateCounter>().All(c => c.Current.Value == (int)Math.Round(250.0 / 1.5)));
}

[Test]
public void TestDynamicRateChange()
{
AddStep("Create Display", recreateDisplay);

AddRepeatStep("Set UR to 100 at 1.0x", () => applyJudgement(10, true, 1.0), 4);
AddRepeatStep("Bring UR to 100 at 1.5x", () => applyJudgement(15, true, 1.5), 4);

AddUntilStep("UR = 100", () => counter.Current.Value == 100.0);
}

private void recreateDisplay()
{
Clear();

Add(counter = new UnstableRateCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(5),
});
AddUntilStep("UR = 100", () => this.ChildrenOfType<UnstableRateCounter>().All(c => c.Current.Value == 100));
}

private void applyJudgement(double offsetMs, bool alt, double gameplayRate = 1.0)
Expand Down
74 changes: 74 additions & 0 deletions osu.Game/Screens/Play/HUD/ArgonUnstableRateCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Skinning;

namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonUnstableRateCounter : UnstableRateCounter, ISerialisableDrawable
{
private ArgonCounterTextComponent text = null!;

protected override double RollingDuration => 250;

private const float alpha_when_invalid = 0.3f;

[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};

[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);

protected override void LoadComplete()
{
base.LoadComplete();

IsValid.BindValueChanged(v => text.FadeTo(v.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint), true);
FinishTransforms(true);
}

public override int DisplayedCount
{
get => base.DisplayedCount;
set
{
base.DisplayedCount = value;
updateWireframe();
}
}

private void updateWireframe()
{
int digitsRequiredForDisplayCount = Math.Max(3, getDigitsRequiredForDisplayCount());

if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length)
text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount);
}

private int getDigitsRequiredForDisplayCount()
{
int digitsRequired = 1;
long c = DisplayedCount;
while ((c /= 10) > 0)
digitsRequired++;
return digitsRequired;
}

protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopRight, "UR")
{
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
}
}
69 changes: 4 additions & 65 deletions osu.Game/Screens/Play/HUD/UnstableRateCounter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,29 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK;

namespace osu.Game.Screens.Play.HUD
{
public partial class UnstableRateCounter : RollingCounter<int>, ISerialisableDrawable
public abstract partial class UnstableRateCounter : RollingCounter<int>
{
public bool UsesFixedAnchor { get; set; }

protected override double RollingDuration => 375;

private const float alpha_when_invalid = 0.3f;
private readonly Bindable<bool> valid = new Bindable<bool>();

private HitEventExtensions.UnstableRateCalculationResult? unstableRateResult;

[Resolved]
private ScoreProcessor scoreProcessor { get; set; } = null!;

public UnstableRateCounter()
protected UnstableRateCounter()
{
Current.Value = 0;
}

[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.BlueLighter;
valid.BindValueChanged(e =>
DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint));
}
public Bindable<bool> IsValid { get; } = new Bindable<bool>();

protected override void LoadComplete()
{
Expand All @@ -67,17 +50,12 @@ private void updateDisplay()

double? unstableRate = unstableRateResult?.Result;

valid.Value = unstableRate != null;
IsValid.Value = unstableRate != null;

if (unstableRate != null)
Current.Value = (int)Math.Round(unstableRate.Value);
}

protected override IHasText CreateText() => new TextComponent
{
Alpha = alpha_when_invalid,
};

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Expand All @@ -88,44 +66,5 @@ protected override void Dispose(bool isDisposing)
scoreProcessor.JudgementReverted -= updateDisplay;
}
}

private partial class TextComponent : CompositeDrawable, IHasText
{
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}

private readonly OsuSpriteText text;

public TextComponent()
{
AutoSizeAxes = Axes.Both;

InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 8, fixedWidth: true),
Text = @"UR",
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}
}
};
}
}
}
}
1 change: 1 addition & 0 deletions osu.Game/Skinning/Skin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer)
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
jsonContent = jsonContent.Replace(@"osu.Game.Skinning.LegacyComboCounter", @"osu.Game.Skinning.LegacyDefaultComboCounter");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.UnstableRateCounter", @"osu.Game.Skinning.Triangles.TrianglesUnstableRateCounter");

try
{
Expand Down
Loading
Loading