Merge pull request #24642 from Givikap120/arod_rate_adjust

Show adjusted AR/OD when using DT/HT
This commit is contained in:
Dean Herbert 2023-12-13 22:17:10 +09:00 committed by GitHub
commit e296730b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 323 additions and 21 deletions

View File

@ -235,5 +235,20 @@ namespace osu.Game.Rulesets.Catch
}),
};
}
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
{
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
double preempt = adjustedDifficulty.ApproachRate < 6
? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5
: 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5;
preempt /= rate;
adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
return adjustedDifficulty;
}
}
}

View File

@ -331,5 +331,26 @@ namespace osu.Game.Rulesets.Osu
}
public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection();
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
{
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
double preempt = adjustedDifficulty.ApproachRate < 5
? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5
: 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5;
preempt /= rate;
adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
double hitwindow = 80.0 - 6 * adjustedDifficulty.OverallDifficulty;
hitwindow /= rate;
adjustedDifficulty.OverallDifficulty = (float)(80.0 - hitwindow) / 6;
return adjustedDifficulty;
}
}
}

View File

@ -264,5 +264,18 @@ namespace osu.Game.Rulesets.Taiko
}), true)
};
}
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
{
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
double hitWindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5;
hitWindow /= rate;
adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitWindow) / 15 + 5);
return adjustedDifficulty;
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Overlays.Mods
{
public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip
{
private FillFlowContainer attributesFillFlow = null!;
private Container content = null!;
private BeatmapDifficulty? originalDifficulty;
private BeatmapDifficulty? adjustedDifficulty;
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 5;
InternalChildren = new Drawable[]
{
content = new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray3,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = 10, Horizontal = 15 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "One or more values are being adjusted by mods that change speed.",
},
attributesFillFlow = new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both
}
}
}
}
},
};
updateDisplay();
}
public void UpdateAttributes(BeatmapDifficulty original, BeatmapDifficulty adjusted)
{
originalDifficulty = original;
adjustedDifficulty = adjusted;
if (IsLoaded)
updateDisplay();
}
private void updateDisplay()
{
attributesFillFlow.Clear();
if (originalDifficulty == null || adjustedDifficulty == null)
return;
attemptAdd("AR", bd => bd.ApproachRate);
attemptAdd("OD", bd => bd.OverallDifficulty);
attemptAdd("CS", bd => bd.CircleSize);
attemptAdd("HP", bd => bd.DrainRate);
if (attributesFillFlow.Any())
content.Show();
else
content.Hide();
void attemptAdd(string name, Func<BeatmapDifficulty, double> lookup)
{
double a = lookup(originalDifficulty);
double b = lookup(adjustedDifficulty);
if (!Precision.AlmostEquals(a, b))
attributesFillFlow.Add(new AttributeDisplay(name, a, b));
}
}
public void SetContent(object content)
{
}
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
public void Move(Vector2 pos) => Position = pos;
private partial class AttributeDisplay : CompositeDrawable
{
public AttributeDisplay(string name, double original, double adjusted)
{
AutoSizeAxes = Axes.Both;
InternalChild = new OsuSpriteText
{
Font = OsuFont.Default.With(weight: FontWeight.Bold),
Text = $"{name}: {original:0.0#} → {adjusted:0.0#}"
};
}
}
}
}

View File

@ -3,21 +3,23 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration;
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 System.Threading;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Mods
{
@ -25,7 +27,7 @@ namespace osu.Game.Overlays.Mods
/// On the mod select overlay, this provides a local updating view of BPM, star rating and other
/// difficulty attributes so the user can have a better insight into what mods are changing.
/// </summary>
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip
{
private StarRatingDisplay starRatingDisplay = null!;
private BPMDisplay bpmDisplay = null!;
@ -47,9 +49,20 @@ namespace osu.Game.Overlays.Mods
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
[Resolved]
private OsuGameBase game { get; set; } = null!;
private IBindable<RulesetInfo> gameRuleset = null!;
private CancellationTokenSource? cancellationSource;
private IBindable<StarDifficulty?> starDifficulty = null!;
private AdjustedAttributesTooltip rateAdjustTooltip = null!;
public ITooltip GetCustomTooltip() => rateAdjustTooltip;
public object TooltipContent => this;
private const float transition_duration = 250;
[BackgroundDependencyLoader]
@ -57,6 +70,8 @@ namespace osu.Game.Overlays.Mods
{
const float shear = ShearedOverlayContainer.SHEAR;
rateAdjustTooltip = new AdjustedAttributesTooltip();
LeftContent.AddRange(new Drawable[]
{
starRatingDisplay = new StarRatingDisplay(default, animated: true)
@ -92,7 +107,6 @@ namespace osu.Game.Overlays.Mods
mods.BindValueChanged(_ =>
{
modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value);
modSettingChangeTracker.SettingChanged += _ => updateValues();
updateValues();
@ -107,6 +121,11 @@ namespace osu.Game.Overlays.Mods
updateCollapsedState();
});
gameRuleset = game.Ruleset.GetBoundCopy();
gameRuleset.BindValueChanged(_ => updateValues());
BeatmapInfo.BindValueChanged(_ => updateValues(), true);
updateCollapsedState();
}
@ -133,11 +152,6 @@ namespace osu.Game.Overlays.Mods
Content.AutoSizeDuration = transition_duration;
}
private void updateCollapsedState()
{
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
}
private void updateValues() => Scheduler.AddOnce(() =>
{
if (BeatmapInfo.Value == null)
@ -160,9 +174,18 @@ namespace osu.Game.Overlays.Mods
bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate;
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(adjustedDifficulty);
mod.ApplyToDifficulty(originalDifficulty);
Ruleset ruleset = gameRuleset.Value.CreateInstance();
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty);
approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate);
overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty);
circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize;
drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate;
@ -170,6 +193,11 @@ namespace osu.Game.Overlays.Mods
overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty;
});
private void updateCollapsedState()
{
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
}
private partial class BPMDisplay : RollingCounter<double>
{
protected override double RollingDuration => 500;

View File

@ -1,15 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osuTK.Graphics;
namespace osu.Game.Overlays.Mods
{
@ -23,11 +28,45 @@ namespace osu.Game.Overlays.Mods
private readonly BindableWithCurrent<double> current = new BindableWithCurrent<double>();
public Bindable<ModEffect> AdjustType = new Bindable<ModEffect>();
/// <summary>
/// Text to display in the top area of the display.
/// </summary>
public LocalisableString Label { get; protected set; }
private readonly EffectCounter counter;
private readonly OsuSpriteText text;
[Resolved]
private OsuColour colours { get; set; } = null!;
private void updateTextColor()
{
Color4 newColor;
switch (AdjustType.Value)
{
case ModEffect.NotChanged:
newColor = Color4.White;
break;
case ModEffect.DifficultyReduction:
newColor = colours.ForModType(ModType.DifficultyReduction);
break;
case ModEffect.DifficultyIncrease:
newColor = colours.ForModType(ModType.DifficultyIncrease);
break;
default:
throw new ArgumentOutOfRangeException(nameof(AdjustType.Value));
}
text.Colour = newColor;
counter.Colour = newColor;
}
public VerticalAttributeDisplay(LocalisableString label)
{
Label = label;
@ -37,6 +76,8 @@ namespace osu.Game.Overlays.Mods
Origin = Anchor.CentreLeft;
Anchor = Anchor.CentreLeft;
AdjustType.BindValueChanged(_ => updateTextColor());
InternalChild = new FillFlowContainer
{
Origin = Anchor.CentreLeft,
@ -45,7 +86,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 +94,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,
@ -63,11 +104,28 @@ namespace osu.Game.Overlays.Mods
};
}
public static ModEffect CalculateEffect(double oldValue, double newValue)
{
if (Precision.AlmostEquals(newValue, oldValue, 0.01))
return ModEffect.NotChanged;
if (newValue < oldValue)
return ModEffect.DifficultyReduction;
return ModEffect.DifficultyIncrease;
}
public enum ModEffect
{
NotChanged,
DifficultyReduction,
DifficultyIncrease,
}
private partial class EffectCounter : RollingCounter<double>
{
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
{

View File

@ -377,6 +377,17 @@ namespace osu.Game.Rulesets
/// <returns>The display name.</returns>
public virtual LocalisableString GetDisplayNameForHitResult(HitResult result) => result.GetLocalisableDescription();
/// <summary>
/// Applies changes to difficulty attributes for presenting to a user a rough estimate of how rate adjust mods affect difficulty.
/// Importantly, this should NOT BE USED FOR ANY CALCULATIONS.
///
/// It is also not always correct, and arguably is never correct depending on your frame of mind.
/// </summary>
/// <param name="difficulty">>The <see cref="IBeatmapDifficultyInfo"/> that will be adjusted.</param>
/// <param name="rate">The rate adjustment multiplier from mods. For example 1.5 for DT.</param>
/// <returns>The adjusted difficulty attributes.</returns>
public virtual BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) => new BeatmapDifficulty(difficulty);
/// <summary>
/// Creates ruleset-specific beatmap filter criteria to be used on the song select screen.
/// </summary>

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -25,10 +26,11 @@ using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Overlays.Mods;
namespace osu.Game.Screens.Select.Details
{
public partial class AdvancedStats : Container
public partial class AdvancedStats : Container, IHasCustomTooltip
{
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
@ -44,6 +46,10 @@ namespace osu.Game.Screens.Select.Details
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty;
private AdjustedAttributesTooltip rateAdjustTooltip;
public ITooltip GetCustomTooltip() => rateAdjustTooltip;
public object TooltipContent => this;
private IBeatmapInfo beatmapInfo;
public IBeatmapInfo BeatmapInfo
@ -80,6 +86,7 @@ namespace osu.Game.Screens.Select.Details
private void load(OsuColour colours)
{
starDifficulty.AccentColour = colours.Yellow;
rateAdjustTooltip = new AdjustedAttributesTooltip();
}
protected override void LoadComplete()
@ -118,15 +125,29 @@ namespace osu.Game.Screens.Select.Details
IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty;
BeatmapDifficulty adjustedDifficulty = null;
if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty))
IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset;
if (baseDifficulty != null &&
(mods.Value.Any(m => m is IApplicableToDifficulty) || mods.Value.Any(m => m is IApplicableToRate)))
{
adjustedDifficulty = new BeatmapDifficulty(baseDifficulty);
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(adjustedDifficulty);
}
mod.ApplyToDifficulty(originalDifficulty);
IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset;
adjustedDifficulty = originalDifficulty;
if (gameRuleset != null)
{
double rate = 1;
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate);
adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty);
}
}
switch (ruleset.OnlineID)
{