diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 4f499cee62..f0b20c68c4 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -238,10 +238,10 @@ namespace osu.Game.Rulesets.Catch public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150)) + 5; public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (bool AR, bool OD) isRateAdjusted) + public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) { BeatmapDifficulty? adjustedDifficulty = null; - isRateAdjusted = (false, false); + rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); if (mods.Any(m => m is IApplicableToDifficulty)) { @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Catch adjustedDifficulty.ApproachRate = ChangeArFromRate(ar, speedChange); - if (adjustedDifficulty.ApproachRate != ar) isRateAdjusted.AR = true; + rateAdjustedInfo.AR = GetRateAdjustType(ar, adjustedDifficulty.ApproachRate); } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7d16806397..a8a54ebf05 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -431,10 +431,10 @@ namespace osu.Game.Rulesets.Mania public double HitwindowFromOd(float OD) => 64.0 - 3 * OD; public float OdFromHitwindow(double hitwindow300) => (float)(64.0 - hitwindow300) / 3; public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (bool AR, bool OD) isRateAdjusted) + public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) { BeatmapDifficulty? adjustedDifficulty = null; - isRateAdjusted = (false, false); + rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); if (mods.Any(m => m is IApplicableToDifficulty)) { @@ -456,7 +456,7 @@ namespace osu.Game.Rulesets.Mania adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - if (adjustedDifficulty.OverallDifficulty != od) isRateAdjusted.OD = true; + rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 250685ec0f..4810be5db2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -335,10 +335,10 @@ namespace osu.Game.Rulesets.Osu public float OdFromHitwindow(double hitwindow300) => (float)(80.0 - hitwindow300) / 6; public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (bool AR, bool OD) isRateAdjusted) + public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) { BeatmapDifficulty? adjustedDifficulty = null; - isRateAdjusted = (false, false); + rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); if (mods.Any(m => m is IApplicableToDifficulty)) { @@ -362,8 +362,8 @@ namespace osu.Game.Rulesets.Osu adjustedDifficulty.ApproachRate = ChangeArFromRate(ar, speedChange); adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - if (adjustedDifficulty.ApproachRate != ar) isRateAdjusted.AR = true; - if (adjustedDifficulty.OverallDifficulty != od) isRateAdjusted.OD = true; + rateAdjustedInfo.AR = GetRateAdjustType(ar, adjustedDifficulty.ApproachRate); + rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 801e46f9c3..a9abbea251 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -266,10 +266,10 @@ namespace osu.Game.Rulesets.Taiko public double HitwindowFromOd(float OD) => 35.0 - 15.0 * (OD - 5) / 5; public float OdFromHitwindow(double hitwindow300) => (float)(5 * (35 - hitwindow300) / 15 + 5); public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (bool AR, bool OD) isRateAdjusted) + public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) { BeatmapDifficulty? adjustedDifficulty = null; - isRateAdjusted = (false, false); + rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); if (mods.Any(m => m is IApplicableToDifficulty)) { @@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Taiko adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - if (adjustedDifficulty.OverallDifficulty != od) isRateAdjusted.OD = true; + rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); } } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 2aea53090a..d0ee9750b2 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -63,6 +64,10 @@ namespace osu.Game.Overlays.Mods [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + [Resolved] + private OsuGameBase game { get; set; } = null!; + private IBindable gameRuleset = null!; + private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; @@ -195,6 +200,9 @@ namespace osu.Game.Overlays.Mods updateCollapsedState(); }); + gameRuleset = game.Ruleset.GetBoundCopy(); + gameRuleset.BindValueChanged(_ => updateValues()); + BeatmapInfo.BindValueChanged(_ => updateValues(), true); } @@ -243,9 +251,12 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); - foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); + (Ruleset.RateAdjustType AR, Ruleset.RateAdjustType OD) rateAdjustType = (Ruleset.RateAdjustType.NotChanged, Ruleset.RateAdjustType.NotChanged); + + Ruleset ruleset = gameRuleset.Value.CreateInstance(); + BeatmapDifficulty adjustedDifficulty = ruleset.GetEffectiveDifficulty(BeatmapInfo.Value.Difficulty, mods.Value, ref rateAdjustType); + approachRateDisplay.RateChangeType.Value = rateAdjustType.AR; + overallDifficultyDisplay.RateChangeType.Value = rateAdjustType.OD; circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize; drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate; diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 60cc875dbb..16d75cd448 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -1,6 +1,8 @@ // 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.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -10,6 +12,9 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osuTK.Graphics; namespace osu.Game.Overlays.Mods { @@ -23,11 +28,43 @@ namespace osu.Game.Overlays.Mods private readonly BindableWithCurrent current = new BindableWithCurrent(); + public Bindable RateChangeType = new Bindable(Ruleset.RateAdjustType.NotChanged); + /// /// Text to display in the top area of the display. /// public LocalisableString Label { get; protected set; } + private EffectCounter counter; + private OsuSpriteText text; + + [Resolved] + private OsuColour colours { get; set; } = null!; + private void updateTextColor() + { + Color4 newColor; + switch (RateChangeType.Value) + { + case Ruleset.RateAdjustType.NotChanged: + newColor = Color4.White; + break; + + case Ruleset.RateAdjustType.DifficultyReduction: + newColor = colours.ForModType(ModType.DifficultyReduction); + break; + + case Ruleset.RateAdjustType.DifficultyIncrease: + newColor = colours.ForModType(ModType.DifficultyIncrease); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(RateChangeType.Value)); + } + + text.Colour = newColor; + counter.Colour = newColor; + } + public VerticalAttributeDisplay(LocalisableString label) { Label = label; @@ -37,6 +74,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft; Anchor = Anchor.CentreLeft; + RateChangeType.BindValueChanged(_ => updateTextColor()); + InternalChild = new FillFlowContainer { Origin = Anchor.CentreLeft, @@ -45,7 +84,7 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + text = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -53,7 +92,7 @@ namespace osu.Game.Overlays.Mods Margin = new MarginPadding { Horizontal = 15 }, // to reserve space for 0.XX value Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold) }, - new EffectCounter + counter = new EffectCounter { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -62,12 +101,11 @@ namespace osu.Game.Overlays.Mods } }; } - private partial class EffectCounter : RollingCounter { protected override double RollingDuration => 500; - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.0"); + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.0#"); protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 0f7655812a..7a09bff4aa 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -389,6 +389,21 @@ namespace osu.Game.Rulesets /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; - public virtual BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (bool AR, bool OD) isRateAdjusted) => (BeatmapDifficulty)baseDifficulty; + public virtual BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) => (BeatmapDifficulty)baseDifficulty; + public enum RateAdjustType + { + NotChanged, + DifficultyReduction, + DifficultyIncrease + } + + protected RateAdjustType GetRateAdjustType(float baseValue, float adjustedValue) + { + if (adjustedValue > baseValue) + return RateAdjustType.DifficultyIncrease; + if (adjustedValue < baseValue) + return RateAdjustType.DifficultyReduction; + return RateAdjustType.NotChanged; + } } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 4a0eed86f8..867d86a69b 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -116,12 +116,12 @@ namespace osu.Game.Screens.Select.Details { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; - (bool AR, bool OD) isRateAdjusted = (false, false); + (Ruleset.RateAdjustType AR, Ruleset.RateAdjustType OD) rateAdjustInfo = (Ruleset.RateAdjustType.NotChanged, Ruleset.RateAdjustType.NotChanged); if (baseDifficulty != null) { Ruleset ruleset = gameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetEffectiveDifficulty(baseDifficulty, mods.Value, ref isRateAdjusted); + adjustedDifficulty = ruleset.GetEffectiveDifficulty(baseDifficulty, mods.Value, ref rateAdjustInfo); } switch (BeatmapInfo?.Ruleset.OnlineID) @@ -140,8 +140,8 @@ namespace osu.Game.Screens.Select.Details } HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate, false); - Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty, isRateAdjusted.OD); - ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate, isRateAdjusted.AR); + Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty, rateAdjustInfo.OD != Ruleset.RateAdjustType.NotChanged); + ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate, rateAdjustInfo.OD != Ruleset.RateAdjustType.NotChanged); updateStarDifficulty(); }